简介
- 二维条码/二维码是用某种
特定的几何图形
按一定规律在平面分布的黑白相间的图形记录数据符号信息的 - 在编码上巧妙地利用构成计算机内部逻辑基础的“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
}