二维码
二维码简介
1.概念
- 二维码:是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;
- 生成二维码:根据给定的信息, 将其按照二维码的编码方式生成一张图片
- 读取二维码:识别二维码图像里面存储的数据
2.二维码的使用场景
- 信息获取(名片、WIFI密码、资料)
- 手机电商(用户扫码、手机直接购物下单)
- 加好友(QQ, 微信, 扫一扫加好友)
- 手机支付(扫描商品二维码,通过银行或第三方支付提供的手机端通道完成支付)
3.二维码生成方式
- 从iOS7开始集成了二维码的生成和读取功能
- 此前被广泛使用的zbarsdk目前不支持64位处理器
- 2015年2月1号起, 不允许不支持64位处理器的APP 上架
4.二维码读取
- 直接从图片中识别,最低支持iOS8.0
- 利用摄像头扫描识别,需要真机设备
生成二维码
1.导入CoreImage框架 一些图片处理操作的功能, 都是用这个框架实现, 比如: 滤镜效果, 毛玻璃, 美颜相机....
#import <CoreImage/CoreImage.h>
2.通过滤镜CIFilter生成二维码
//创建二维码滤镜
let filter = CIFilter(name: "CIQRCodeGenerator")
//注意,最好恢复滤镜的默认设置
filter?.setDefaults()
//设置滤镜输入数据 KVC
let data = enterField.text.data(using: .utf8)
filter?.setValue(data, forKey: "inputMessage")
//从二维码滤镜终获取结果图片
var image = filter?.outputImage
//处理模糊的图片
let transform = CGAffineTransform.init(scaleX: 20, y: 20)
image = image?.applying(transform)
//显示图片
let resultImage = UIImage(ciImage: image!)
codeImageView.image = resultImage
自定义二维码
所谓自定义二维码, 就是指给二维码添加一些图片(前景或者背景图片), 或者改变下颜色
可以添加前景图片的前提是因为二维码具备一定的"纠错率"
如果二维码被部分遮挡, 可以根据其他部分, 计算出遮挡部分内容;
但是要保证三个角不能被遮挡; 三个角用作扫描定位使用(可能用户倒着拍, 斜着拍等等)
通过KVC 设置滤镜的 inputCorrectionLevel (纠错率) @"L", @"M", @"Q", @"H" 中的一个
- L水平 7%的字码可被修正
- M水平 15%的字码可被修正
- Q水平 25%的字码可被修正
- H水平 30%的字码可被修正
filter?.setValue("M", forKey: "inputCorrectionLevel")
纠错率越高,扫描花费的时间越多
创建中心图片
func createCenterImage(qrCodeImage:UIImage, centerImage:UIImage) -> UIImage?{
UIGraphicsBeginImageContext(qrCodeImage.size)
qrCodeImage.draw(in: CGRect(x: 0, y: 0, width: qrCodeImage.size.width, height: qrCodeImage.size.height))
let centerImageWH:CGFloat = 100.0
centerImage.draw(in: CGRect(x: (qrCodeImage.size.width - centerImageWH) * 0.5, y: (qrCodeImage.size.height - centerImageWH) * 0.5, width: centerImageWH, height: centerImageWH))
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultImage
}
识别二维码
一个完整的示例
import UIKit
class QRCodeRecognitionController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var tableView: UITableView!
lazy var datas = [[String:Any]]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = self
tableView.delegate = self
}
@IBAction func recognition(_ sender: UIButton) {
//获取需要识别的图片
var image = imageView.image
//识别
//创建一个二维码探测器
let dector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh])
//直接探测二维码的特征,获得的是个数组,因为有可能右多个二维码图片
let features = dector?.features(in: CIImage(image: image!)!)
datas = []
//解析每一个二维码特征,获取一些关键信息
for feature in features! {
let ciFeature = feature as! CIQRCodeFeature
let recogImage = drawQRCodeImage(bounds:ciFeature.bounds,sourceImage:image!)//注意,这个bounds的坐标系是上下颠倒的
datas.append(["image":recogImage!,"message":ciFeature.messageString ?? "无法识别"])
}
tableView.reloadData()
}
func drawQRCodeImage(bounds:CGRect,sourceImage:UIImage) -> UIImage? {
print("sourceImage size:\(sourceImage.size)")
print("bounds:\(bounds)")
UIGraphicsBeginImageContext(bounds.size)
sourceImage.draw(in: CGRect(x: -bounds.origin.x, y: -(sourceImage.size.height - bounds.height - bounds.origin.y), width: sourceImage.size.width, height: sourceImage.size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
extension QRCodeRecognitionController:UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.imageView?.image = datas[indexPath.row]["image"] as? UIImage
cell.textLabel?.text = datas[indexPath.row]["message"] as? String
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let str = datas[indexPath.row]["message"] as? String
if str == nil { return }
if str!.contains("http") {
UIApplication.shared.open(URL(string:str!)!, options: [:], completionHandler: nil)
}
}
}
扫描二维码
一个完整的示例
import UIKit
import AVFoundation
class QRCodeScanController: UIViewController {
var session:AVCaptureSession?
var layer:AVCaptureVideoPreviewLayer?
var resultStr:String?
@IBOutlet weak var containerView: UIView!
@IBOutlet weak var borderImageView: UIImageView!
@IBOutlet weak var scanlineImageView: UIImageView!
@IBOutlet weak var bottomCons: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
containerView.clipsToBounds = true
configScan()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startAnimation()
removeFrame()
//启动会话,让输入开始采集数据,输出对象,开始处理数据
session?.startRunning()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination.isKind(of: QRScanResultController.self) {
(segue.destination as! QRScanResultController).text = resultStr!
}
}
}
extension QRCodeScanController {
func startAnimation() {
bottomCons.constant = containerView.frame.height
view.layoutIfNeeded()
bottomCons.constant = -containerView.frame.height
UIView.animate(withDuration: 2) {
UIView.setAnimationRepeatCount(MAXFLOAT)
self.view.layoutIfNeeded()
}
}
}
extension QRCodeScanController: AVCaptureMetadataOutputObjectsDelegate {
func configScan() {
//设置输入
//获取摄像头设备
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
//把摄像头设备当做输入设备
let input = try? AVCaptureDeviceInput(device: device)
guard input != nil else { return }
//设置输出
let output = AVCaptureMetadataOutput()
//设置结果处理的代理
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//创建会话,连接输入和输出
session = AVCaptureSession()
guard (session?.canAddInput(input))! && (session?.canAddOutput(output))!
else { return }
session?.addInput(input)
session?.addOutput(output)
//设置可以二维码的码制, 必须在输出添加到会话之后才可以设置,否则会崩溃
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
//设置指定的区域扫描
let screenBounds = UIScreen.main.bounds
let outX:CGFloat = (containerView.frame.origin.x - 20) / screenBounds.size.width
let outY:CGFloat = (containerView.frame.origin.y - 20 + 44) / screenBounds.size.height
let outW:CGFloat = (containerView.frame.size.width + 20) / screenBounds.size.width
let outH:CGFloat = (containerView.frame.size.height + 20 + 44) / screenBounds.size.height
output.rectOfInterest = CGRect(x: outY, y: outX, width: outH, height: outW)
//添加视频预览图层
layer = AVCaptureVideoPreviewLayer(session: session!)
layer?.frame = view.layer.bounds;
view.layer.insertSublayer(layer!, at: 0)
}
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
removeFrame()
for meta in metadataObjects {
if (meta as AnyObject).isKind(of:AVMetadataMachineReadableCodeObject.self) {
let resultObj = layer?.transformedMetadataObject(for: meta as! AVMetadataObject)
let qrCodeObj = resultObj as! AVMetadataMachineReadableCodeObject
//print(qrCodeObj.stringValue)
resultStr = qrCodeObj.stringValue
//print(qrCodeObj.corners)//代表二维码的4个角,但是一个比例数值,需要转换
drawFrame(qrCodeObj: qrCodeObj)
if resultStr != nil {
session?.stopRunning()
print("resultStr:\(resultStr!)")
performSegue(withIdentifier: "scanResult", sender: nil)
}
}
}
}
func drawFrame(qrCodeObj:AVMetadataMachineReadableCodeObject) {
let corners = qrCodeObj.corners
//创建一个图形层用于绘制
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.green.cgColor
shapeLayer.lineWidth = 3
//根据四个点创建一个路径
let path = UIBezierPath()
var index = 0
for corner in corners! {
let pointDict = corner as! CFDictionary
let point = CGPoint(dictionaryRepresentation: pointDict)
if index == 0 {
path.move(to: point!)
} else {
path.addLine(to: point!)
}
index += 1
}
path.close()
shapeLayer.path = path.cgPath
layer?.addSublayer(shapeLayer)
}
func removeFrame() {
guard let sublayers = layer?.sublayers else { return }
for sublayer in sublayers {
if sublayer.isKind(of: CAShapeLayer.self) {
sublayer.removeFromSuperlayer()
}
}
}
}