二维码

news/2025/6/6 13:55:34

简介

  • 二维条码/二维码是用某种特定的几何图形按一定规律在平面分布的黑白相间的图形记录数据符号信息的
  • 在编码上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息
  • 通过图象输入设备或光电扫描设备自动识读以实现信息自动处理

特点

  • 每种码制有其特定的字符集
  • 每个字符占有一定的宽度
  • 具有一定的校验功能

功能

  • 信息获取(名片、地图、WIFI密码、资料)
  • 网站跳转(跳转到微博、手机网站、网站)
  • 广告推送(用户扫码,直接浏览商家推送的视频、音频广告)
  • 手机电商(用户扫码、手机直接购物下单)
  • 防伪溯源(用户扫码、即可查看生产地;同时后台可以获取最终消费地)
  • 优惠促销(用户扫码,下载电子优惠券,抽奖)
  • 会员管理(用户手机上获取电子会员信息、VIP服务)
  • 手机支付(扫描商品二维码,通过银行或第三方支付提供的手机端通道完成支付)

优点

  • 高密度编码,信息容量大:可容纳多达1850个大写字母或2710个数字或1108个字节,或500多个汉字,比普通条码信息容量约高几十倍
  • 编码范围广:该条码可以把图片、声音、文字、签字、指纹等可以数字化的信息进行编码,用条码表示出来;可以表示多种语言文字;可表示图像数据
  • 容错能力强,具有纠错功能:这使得二维条码因穿孔、污损等引起局部损坏时,照样可以正确得到识读,损毁面积达50%仍可恢复信息 4.译码可靠性高:它比普通条码译码错误率百万分之二要低得多,误码率不超过千万分之一 5.可引入加密措施:保密性、防伪性好 6.成本低,易制作,持久耐用 7.条码符号形状、尺寸大小比例可变 8.二维条码可以使用激光或CCD阅读器识读

安全提示 —— 不要见码就扫

更多内容请参阅:http://baike.baidu.com/view/132241.htm

用户界面搭建

文件准备

  • 新建 QRCodeViewController.swift & QRCode.storyboard
  • 在 Storyboard 中添加 UIViewController 并且指定子类
  • 在视图控制器上嵌入 UINavigationController

加载视图控制器

  • 在 HomeTableViewController 中增加 scanQRCode 函数,显示 QRCode 控制器
@IBAction func scanQRCode() { presentViewController(UIStoryboard.initViewController("QRCode"), animated: true, completion: nil) } 

界面布局

  • 添加素材
  • 将 Navigation Bar 的 Style 设置为 Black
  • 增加 UITabBar

    • 在之前版本中,分 二维码扫描 和 条形码扫描 两种方式
  • 在 AppDelegate 中增加设置外观函数

///  设置外观(一经设置,全局有效,外观设置要尽量的早)
private func setupAppearance() { UINavigationBar.appearance().tintColor = UIColor.orangeColor() UITabBar.appearance().tintColor = UIColor.orangeColor() } 
  • 在屏幕中心添加扫描视图

    • 垂直居中
    • 水平居中
    • 宽度:300
    • 高度:300
    • 背景色:clearColor
  • 在扫描视图内部添加边框图像视图,边框图片切片如下

Tabbar选择切换

  • TabBar 的 Style 修改为 Black
  • 默认选中第一项
@IBOutlet weak var tabBar: UITabBar!override func viewDidLoad() { super.viewDidLoad() tabBar.selectedItem = tabBar.items![0] as? UITabBarItem } 
  • 通过代理监听 Item 选中事件
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem!) {heightConstraint.constant = weightConstraint.constant * (item.tag == 1 ? 0.5 : 1) } 

冲击波动画

  • 添加冲击波图片,并且设置相对于 扫描视图 的参照

  • 实现冲击波动画
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) scanAnimation() } /// 冲击波动画 func scanAnimation() { // 停止图层动画 scanImage.layer.removeAllAnimations() // 设定动画初始约束 self.topScanConstraint.constant = -heightConstraint.constant // 更新视图布局 self.view.layoutIfNeeded() // 开始动画 UIView.animateWithDuration(2.0, animations: { () -> Void in self.topScanConstraint.constant = self.heightConstraint.constant UIView.setAnimationRepeatCount(MAXFLOAT) self.view.layoutIfNeeded() }) } // MARK: - UITabBarDelegate func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem!) { heightConstraint.constant = weightConstraint.constant * (item.tag == 1 ? 0.5 : 1) scanAnimation() } 

注意:动画函数的前三句话非常重要!

细节处理

  • 勾选扫描视图的 Clip Subviews 属性
  • 修改 冲击波 初始 Top 约束数值 -300

扫描二维码

第三方框架

  • ZXing Android使用多
  • ZBar iOS使用多

  • 提示:以上两个框架都是老牌二维码框架,不过都不支持 64 位

  • 目前在 iOS 开发中普遍使用苹果的 AVFoundation 框架,但是不支持图片识别功能
  • AVFoundation 只支持通过摄像头扫描识别

识别原理

代码实现

  • 拍摄会话
/// 拍摄会话,是扫描的桥梁
lazy var session: AVCaptureSession = {return AVCaptureSession() }() 
  • 摄像头输入设备
/// 摄像头输入
lazy var videoInput: AVCaptureDeviceInput? = {// 获取摄像头设备if let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) { return AVCaptureDeviceInput(device: device, error: nil) } return nil }() 
  • 数据输出
/// 数据输出
lazy var dataOutput: AVCaptureMetadataOutput = {return AVCaptureMetadataOutput() }() 
  • 建立通道
func scan() {// 1. 添加输入设备 if !session.canAddInput(videoInput) { print("无法添加输入设备") return } session.addInput(videoInput) // 2. 添加输出设备 if !session.canAddOutput(dataOutput) { println("无法添加输出设备") return } session.addOutput(dataOutput) println(dataOutput.availableMetadataObjectTypes) } 

注意,一定要把输出设备添加到会话后,才有可用数据类型

  • 设置数据类型、代理,启动会话
// 2.1 设置扫描数据类型(全部支持)
dataOutput.metadataObjectTypes = dataOutput.availableMetadataObjectTypes// 2.2 设置输出代理
dataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())// 3. 启动会话
session.startRunning()
  • 实现协议方法
// MARK: - AVCaptureMetadataOutputObjectsDelegate
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) { println(metadataObjects) } 

必须要启动会话,才能开始扫描

  • 添加预览视图
/// 预览图层
lazy var previewLayer: AVCaptureVideoPreviewLayer = {let layer = AVCaptureVideoPreviewLayer(session: self.session) layer.frame = self.view.bounds return layer }() 

进一步体会一下此处的 self.

  • 闭包内部需要使用 self.
  • 在 OC 中使用 self. 能够调用属性的 getter 方法,确保对象一定能够拿到

  • 完整的扫描函数代码

/// 扫描函数
func scan() { // 1. 添加输入设备 if !session.canAddInput(videoInput) { print("无法添加输入设备") return } session.addInput(videoInput) // 2. 添加输出设备 if !session.canAddOutput(dataOutput) { println("无法添加输出设备") return } session.addOutput(dataOutput) println(dataOutput.availableMetadataObjectTypes) // 2.1 设置扫描数据类型(全部支持) dataOutput.metadataObjectTypes = dataOutput.availableMetadataObjectTypes // 2.2 设置输出代理 dataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue()) // 3. 添加预览图层 view.layer.insertSublayer(previewLayer, atIndex: 0) // 4. 启动会话 session.startRunning() } 
  • 修改扫描代理方法,提取数值
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {for dataObject in metadataObjects { println(dataObject) } }

绘制线条

AVMetadataMachineReadableCodeObject

  • bounds
  • corners

代码实现

  • 绘制图层
lazy var drawLayer: CALayer = {let layer = CALayer()layer.frame = self.view.bounds return layer }() 
  • 添加图层
// 3. 添加图层
view.layer.insertSublayer(drawLayer, atIndex: 0)
view.layer.insertSublayer(previewLayer, atIndex: 0)// 4. 启动会话
session.startRunning()

注意:一定要用 insertSublayer,否则会遮挡住 TabBar

  • 坐标转换
for dataObject in metadataObjects as! [AVMetadataMachineReadableCodeObject] {println(dataObject) let obj = previewLayer.transformedMetadataObjectForMetadataObject(dataObject) println(obj) } 
# 转换前
<AVMetadataMachineReadableCodeObject: 0x170220720,
type="org.iso.QRCode",
bounds={ 0.4,0.4 0.1x0.2 }>
corners { 0.4,0.6 0.5,0.6 0.5,0.4 0.4,0.4 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"# 转换后
<AVMetadataMachineReadableCodeObject: 0x170622cc0,
type="org.iso.QRCode",
bounds={ 116.6,224.9 79.5x80.0 }>
corners { 116.6,226.1 117.2,304.4 196.1,304.9 195.7,224.9 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"

转换的目的是将采集到的坐标转换成能够识别的坐标数值

  • 创建路径
///  创建路径
///
///  :param: points The value of this property is an array of CFDictionary objects
///
/// :returns: 贝赛尔路径 private func createPath(points: NSArray)-> UIBezierPath { let path = UIBezierPath() var point = CGPoint() var index = 0 // 起始点 CGPointMakeWithDictionaryRepresentation(points[index++] as! CFDictionaryRef, &point) path.moveToPoint(point) // 遍历剩余的点 while index < points.count { CGPointMakeWithDictionaryRepresentation(points[index++] as! CFDictionaryRef, &point) path.addLineToPoint(point) } // 关闭路径 path.closePath() return path } 

注意 corners 是保存 CFDictionary 对象的数组

  • 绘制条码边线
/// 绘制编码变线
private func drawCodeCorners(codeObject: AVMetadataMachineReadableCodeObject) { if codeObject.corners.count == 0 { return } // 建立形状图层 let shapeLayer = CAShapeLayer() shapeLayer.strokeColor = UIColor.greenColor().CGColor shapeLayer.fillColor = UIColor.clearColor().CGColor shapeLayer.lineWidth = 4 shapeLayer.path = createPath(codeObject.corners).CGPath // 添加形状图层 drawLayer.addSublayer(shapeLayer) } 

注意:一定要判断 corners 是否包含数据,否则会崩溃

  • 清空绘图图层
///  清空绘图图层
private func clearDrawLayer() { if drawLayer.sublayers != nil { for l in drawLayer.sublayers { l.removeFromSuperlayer() } } } 

注意:一定要判断 subLayers 否则会崩溃

  • 调整后的代码
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {clearDrawLayer()for dataObject in metadataObjects as! [AVMetadataMachineReadableCodeObject] { // 转换编码对象 let codeObject = previewLayer.transformedMetadataObjectForMetadataObject(dataObject) as! AVMetadataMachineReadableCodeObject drawCodeCorners(codeObject) println(codeObject) } }

生成二维码

///  生成二维码
public func generateImage(stringValue: String, avatarImage: UIImage?, avatarScale: CGFloat = 0.25, color: CIColor = CIColor(red: 0, green: 0, blue: 0), backColor: CIColor = CIColor(red: 1, green: 1, blue: 1)) -> UIImage? { let qrFilter = CIFilter(name: "CIQRCodeGenerator") qrFilter.setDefaults() qrFilter.setValue(stringValue.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false), forKey: "inputMessage") let ciImage = qrFilter.outputImage let colorFilter = CIFilter(name: "CIFalseColor") colorFilter.setDefaults() colorFilter.setValue(ciImage, forKey: "inputImage") colorFilter.setValue(color, forKey: "inputColor0") colorFilter.setValue(backColor, forKey: "inputColor1") let transform = CGAffineTransformMakeScale(5, 5) let transformedImage = colorFilter.outputImage.imageByApplyingTransform(transform) let image = UIImage(CIImage: transformedImage) if avatarImage != nil && image != nil { return insertAvatarImage(image!, avatarImage: avatarImage!, scale: avatarScale) } return image } func insertAvatarImage(codeImage: UIImage, avatarImage: UIImage, scale: CGFloat) -> UIImage { let rect = CGRectMake(0, 0, codeImage.size.width, codeImage.size.height) UIGraphicsBeginImageContext(rect.size) codeImage.drawInRect(rect) let avatarSize = CGSizeMake(rect.size.width * scale, rect.size.height * scale) let x = (rect.width - avatarSize.width) * 0.5 let y = (rect.height - avatarSize.height) * 0.5 avatarImage.drawInRect(CGRectMake(x, y, avatarSize.width, avatarSize.height)) let result = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return result }
 

 

转载于:https://www.cnblogs.com/Milo-CTO/p/4771200.html

文章来源:https://blog.csdn.net/weixin_30905133/article/details/95963840
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-17038.html

相关文章

没几把刷子,老板请你干嘛?

老板的世界 请你来是解决问题而不是制造问题&#xff1b; 如果你不能发现问题或解决不了问题&#xff0c;你本人就是一个问题&#xff1b; 你能解决多大的问题&#xff0c;你就坐多高的位子&#xff1b; 你能解决多少问题&#xff0c;你就能拿多少薪水&#xff1b; 让解决问题的…

mysql中的datetime_mysql中DATETIME、DATE和TIMESTAMP的区别

DATETIME、DATE 和 TIMESTAMP 类型是相似的。本篇描述了它们的特性以及它们的相似点与不同点。DATETIME 类型可用于需要同时包含日期和时间信息的值。MySQL 以 YYYY-MM-DD HH:MM:SS 格式检索与显示 DATETIME 类型。支持的范围是 1000-01-01 00:00:00 到 9999-12-31 23:59:59。(…

SQL Case when 的使用方法

Case具有两种格式。简单Case函数和Case搜索函数。 --简单Case函数 CASE sex WHEN 1 THEN 男 WHEN 2 THEN 女 ELSE 其他 END --Case搜索函数 CASE WHEN sex 1 THEN 男 WHEN sex 2 THEN 女 ELSE 其他 END 这两种方式&#xff0c;可以实现相同的功能。简单Case函数的写法相对比较…

SQL语言学习-数据定义语言

Sql语言至今已经有6个版本。SQL查询语言包括了所有对数据的操作命令&#xff0c;这些操作可分为四类&#xff1a;数据定义语言&#xff08;DDL&#xff09;、数据操纵语言&#xff08;DML&#xff09;、数据控制语言&#xff08;DCL&#xff09;和嵌入式SQL语言。 数据定义语言…

mysql is null优化_如何优化mysql的is null?

给大家安利下MySQL的最新手册&#xff0c;就是如何优化mysql的is null&#xff1f;包括如果编写mysql is null的语句。《MySQL使用手册》第二章主要讲述了优化程序如何处理WHERE子句&#xff0c;例子中使用了SELECT语句&#xff0c;但是在DELETE和UPDATE语句中对WHERE子句的优化…

Runloop

Run Loop就是一个事件处理的循环&#xff0c;用来不停的调动工作以及处理输入事件。使用Run Loop的目的就是节省CPU效率&#xff0c;线程在有工作的时候忙于工作&#xff0c;而没工作的时候处于休眠状态。 理解对按钮添加事件这个操作[btn addTarget:self action:selector:(btn…

xamarin 开发向导

http://msdn.microsoft.com/zh-tw/vstudio/dn394307.aspx http://blog.csdn.net/lilanfei/article/details/39671747转载于:https://www.cnblogs.com/tiger5/p/4112692.html

mysql 执行计划使用索引优先级_MySQl性能优化,MySQl索引优化,MySQl执行计划使用实战经历...

补充&#xff1a;看到好多朋友后台留言说对SQL优化感兴趣&#xff0c;我又重新整理了下文章&#xff0c;将更多关于sql优化的知识分享出来&#xff0c;SQL优化也是面试中必问的知识点&#xff0c;一定要牢记优化套路。场景我用的数据库是MySQL5.6&#xff0c;下面简单的介绍下场…

Hive SQL的编译过程

转自: http://tech.meituan.com/hive-sql-to-mapreduce.html &#xff08;美团技术团队&#xff09; Hive是基于Hadoop的一个数据仓库系统&#xff0c;在各大公司都有广泛的应用。美团数据仓库也是基于Hive搭建&#xff0c;每天执行近万次的Hive ETL计算流程&#xff0c;负责每…

在浏览器地址栏里显示图片 favicon.ico

在iPhone/iPad等苹果移动设备上&#xff0c;可以把网站”添加至主屏幕”&#xff0c;添加时的图标可以在HTML中自定义设置图片。 可以使用apple-touch-icon和apple-touch-icon-precomposed这两种方法&#xff0c;两者区别是&#xff1a; 使用apple-touch-icon属性为“增加一层透…