• 二维码


    简介

    • 二维条码/二维码是用某种特定的几何图形按一定规律在平面分布的黑白相间的图形记录数据符号信息的
    • 在编码上巧妙地利用构成计算机内部逻辑基础的“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
    }
     
  • 相关阅读:
    CMD指令
    六种Socket I/O模型幽默讲解
    性格与职业的选择
    为什么主引导记录的内存地址是0x7C00?
    pandas数据分析第二天
    pandas数据结构和介绍第一天
    tornado options
    tornado.web.StaticFileHandler
    mysql多条更新
    pandas
  • 原文地址:https://www.cnblogs.com/Milo-CTO/p/4771200.html
Copyright © 2020-2023  润新知