构建一个二维码生成器与Core Image核心图像过滤器

原文:Building a QR Code Generator with Core Image Filters

翻译:Juqiang Xie

我在Appcoda上的第一个教程的第一个部分是介绍如何在ios中创建二维码扫描器,在那时,关于二维码的教程还只有用objective-c来写的,还未有用swift来实现的。后来我的朋友Simon写了一个新的。这段时间,所有人都疯狂的爱上了swift这门新的语言。到了有关于这个主题的文章第一次在这里发表的时候,二维码在广告和营销中被使用的越来越广泛,并且许多的开发者也陷入了制作二维码扫描器的争夺战中。今天二维码已经遍布过个角落,杂志,报纸,电视,广告,网页,有的还印在了T血衫上等等,不胜枚举。另一方面,二维码扫描的应用在AppStore上也是数不胜数。那么当你明白二维码是为广告商和开发者的一个感兴趣的部分(而不仅仅),因为它新且易于管理。


在很长一段时间后本教程又回到了讨论有关二维码的内容上,自从第一次有关二维码教程出现这之间有许了多教程,但这次要完成的目标不同。我的打算不是想要重复的谈论二维码阅读器,毕竟我们已经有了那么多的教程(看看之前的链接)。这次我的目标是向你展示如何创建二维码生成器,请相信我,因为你将看到创建二维码阅读器也是一样的容易(甚至更容易),但首先,让我来告诉你一些你该关心的事情。

在ios7系统之前,创建一个二维码生成器是一件很麻烦的事情。当时现有的资源并不多,许多的开发者不得不避免创建自定义的代码,最简单的做法就是选择2-3个不同的现成的库加入到他们的应用中去。但是谢天谢地,所有的这些现在都已成为历史。在推出ios7之前,苹果公司也是毫不留情的拒绝任何有关二维码代码的任务(阅读和生成)。对于二维码阅读器,AVFoundation 框架是现在每一个开发者需要的工具。对于二维码生成器,程序员要做的唯一的事就是CoreImage框架,更具体的CoreImage核心图像过滤器。

我们很清楚的知道,自从ios7问世,二维码生成器作为一个CIImage 类(CoreImage 图像)只需使用一个名字叫做CIQRCodeGenerator的特殊过滤器。改过滤器提供所有你想要转化为二维码图片的数据,ios将管理并执行这个复杂的工作。只要你不损坏它或使它不可读取,返回的二维码图片你可以进行任意的操作。

在接下来的教程里,你将发现如何快速且容易的创建二维码,并且你将会看到一些更好的实现技巧。读完这篇文章你将能够在ios环境生成二维码图片,并且你可以放大到任意的尺寸。稍后你们会明白,创建一个二维码后你必须去处理它的理想输出大小,使得图片看起来清晰。请注意我提出来的处理方法并不是唯一的方法,仅仅是我个人喜欢的方法,当然接下来你将会看到其他的处理方法。

以上说了那么多,是时候来说说一些您真正感兴趣的事情了。

例子应用概述

正如你可能怀疑,本教程的演示应用程序不会复杂。下面的截图先了解下我们要做什么

与许多其他教程应用程序不同的是,这一次我们将从头开始创建我们的演示应用程序,因为它很简单,我们的接口分为以下几个部分:

1.一个文本编辑框,提供输入任意的文本或者是url地址来生成二维码图片
2.一个按钮控件,点击可以生成二维码或者是删除二维码两种功能。
3.一个图像视图,显示生成的二维码图片
4.一个滑块控件,调整图片是生成的效果质量

  • 记住要生成的二维码是CoreImage 核心图像 (CIImage),所以我们将把它转化成UIImage 类,除此之外,我们设置它到图像的视图上之前将做一些步骤,但是我留了些细节在后面。

所以,如果你已经准备好,启动你的xcode让我们一起开始我们的新应用吧。创建你第一个二维码之前你还有两个步骤。

创建UI

当你启动xcode 便选择相应的选项,这就是创建实际项目的第一步。在第一步的引导中,选择Single View Application 模板,然后设置项目名称为QRCodeGen,并选择发开语言为Swift. 当选择完成的时候,会让你在硬盘中选择一个位置来保存你的项目,以便更好的继续。

第二步是实际内容部分,涉及到用户界面的设置(UI). 所以,在工程目录中,点击Main.storyboard 文件,出现界面构建器,下面的截图可以说明如何在ViewController 场景中添加所以必须的子视图。


开始从类库中拖入一个UItextfield 到默认的视图View中,并设置它的frame 如下:

  • X = 16
  • Y = 28
  • Width = 568
  • Height = 30 (you can’t change that)
现在不需要对textfield 进行更多的配置,稍后我们将连同其他的子视图控件进行条件约束设置。

现在添加一个按钮到视图中,设置其属性如下:

  • Title = “Generate”
  • Text color = White
  • Background color (Hex) = #F39C12
并且同时设置它的frame如下:

  • X = 464
  • Y = 66
  • Width = 120
  • Height = 30
下一步,拖入一个UIImageView 类 并添加到视图中,我们将只指定她的frame如下:

  • X = 200
  • Y = 200
  • Width = 200
  • Height = 200
最后,添加一个UISlider 类,设置属性如下:

  • Minimum value = 0
  • Maximum value = 2
  • Current value = 1
  • Min Track Tint (Hex) = #C0392B
  • Thumb Tint (Hex) = #D35400
  • Hidden = true (check it)
它的frame:

  • X = 189
  • Y = 550
  • Width = 223
  • Height = 31 (it can’t be changed)
现在让我们指定条件约束。我们将指定其中一些,然后让xcode为我们创建出其他缺失的。第一个开始我们选择ImageView(图像视图).然后点击右边底部的Pin 按钮。检测它的宽度和高度约束.我们想让ImageView总是拥有相同的大小。


接下来,任然是选择ImageView,点击Align 按钮 然后设置 Horizontal Center (水平中心) 约束和Vertical Center(垂直中心)约束.


ImageView的条件约束已经完成,现在选择Slider(滑块控件),点击Pin 按钮.和设置ImageView类似,也是要设置宽度和高度,下一步点击Align 按钮只要选择水平中心约束选项。

以上步骤都完成后,我们将允许Xcode去自动添加其他的约束。首先我们选择ViewController 的场景类,如下图:


然后点击右下边底部Resolve Auto Layout Issues按钮(Pin 按钮旁边),在底部菜单中间选择添加Missing Constraints 选项(在所有视图的视图控制器部分)

最后,让我们声明和链接一些IBOutlet属性 和 IBAction 方法。 首先,打开ViewController.swift 文件 并添加如下属性:

@IBOutlet weak var textField: UITextField!


@IBOutlet weak var imgQRCode: UIImageView!


@IBOutlet weak var btnAction: UIButton!


@IBOutlet weak var slider: UISlider!



通过他们的名字和数据类型,你能够知道每个视图控件对应的属性。所以,回到界面构建器进行对应的连接(我确信你知道该如何操作,这里我就不做介绍了)

我们也需要两个IBAction 方法,所以返回到ViewController.swift 文件 定义方法如下:

@IBAction func performButtonAction(sender: AnyObject) {


}

@IBAction func changeImageViewScale(sender: AnyObject) {


}


最后,在界面构建器中你连接以上的方法到相应的视图控件中,第一个方法必须连接到创建好的按钮的Touch Up Inside 事件中,第二个方法必须连接到Slider(滑块)中的Value Changed 中去。

当你完成了这些操作,你就可以进行接下来的步骤了,UI的工作已经结束,让我们来看看如何生成二维码图片。

生成一个二维码

根据目前我们所完成的,你很容易能猜到我们是通过使用performButtonAction,IBAction 方法来生成新的二维码图片。在这之前,让我们在类的顶部定义一个新的变量如下:
var qrcodeImage: CIImage!
上面这个变量我们将用来保存二维码的图像数据,别忘了我们是用核心图像过滤器来生成的,因此它将是一个CoreImage类型的图片,然后我们将它转换成UIImage类型,那么现在生成的就是我们接下来需要用到的。
现在让我们首先来看看performButtionAction方法,它将执行两个功能:一是生成二维码,二是移除已经存在的二维码,我们通过检测qrcodeImage 属性为 nil 或者不在方法体中来区分判断不同情况.此外,通过点击按钮我们将改变按钮的title,“生成” 变为 “清除”(再次点击则相反)。
每一步看下来,目前我们所要关注的是二维码的生成。很显然,如果用户不输入文本是无法生成二维码的,我们必须确保这一点,那么下面你将看到一个简单的方法来避免这种情况,然而如果在真实的项目中,你应该做的更友好些。

@IBAction func performButtonAction(sender: AnyObject) {
    if qrcodeImage == nil {
        if textField.text == "" {
            return
        }
    }
}

这个方法我们知道如果textfield 任然为空,将不执行任何事情。
现在我们来看看如何生成二维码,介绍的时候我说过,创建一个新的CoreImage 过滤器是通过类名位CIQRCodeGenerator的类,指定两个参数并得到输出的图像,即为二维码图像。指定所需的两个参数是:

1.输入的信息:初始化你想要转化成二维码图片的数据,实际上,它总是一个NSData类,所以确保你用的每一个字符串或者其他类转化为NSData类型。
2.输入效验等级:它表示纠正多少额外的数据到输出的二维码中。四个字符串的值:L,M,Q,H,每一个值对应的错误恢复能力( 7%, 15%, 25%, 30%),值越高,输出的二维码图像越大。
代码如下:
@IBAction func performButtonAction(sender: AnyObject) {
    if qrcodeImage == nil {
        ...

        let data = textField.text.dataUsingEncoding(NSISOLatin1StringEncoding, allowLossyConversion: false)

        let filter = CIFilter(name: "CIQRCodeGenerator")

        filter.setValue(data, forKey: "inputMessage")
        filter.setValue("Q", forKey: "inputCorrectionLevel")

        qrcodeImage = filter.outputImage
    }
}

以上5行代码就是生存二维码所必须的所有代码,它包含你在文本框输入的数据内容。注意苹果推荐使用NSISOLatin1StringEncoding 代替NSUTF8StringEncoding,不管怎么样效果也不错。
现在让我们来测试下app看看会发生什么。在运行之前,添加下面这些代码到按钮的方法中去:
@IBAction func performButtonAction(sender: AnyObject) {
    if qrcodeImage == nil {
        ...

        imgQRCode.image = UIImage(CIImage: qrcodeImage)

        textField.resignFirstResponder()
    }
}
所有我们做的是为了将CIImage类型转为UIImage 类型 ,通过调用Textfield的resignFirstResponder()方法来隐藏键盘。
添加完成之后,我们运行我们的应用,你可以在模拟器或者真机上运行。当应用启动打开的时候,到文本框输入你想要输入的文本内容,或者是url地址如下图所示,然后点击生成按钮,就可以看到生成的二维码了。


恭喜你,你终于创建好你第一个二维码图片了。
根据你使用的字符串长度和使用的效验等级,生成的二维码密度可能有所不同。然而这并不是最重要的,真正重要的是二维码图片大小不等于图片视图控件的大小。我们关心得到的大小,因为视图控件的内容默认是会放大充满整个视图控件。导致出现模糊效果。不仅仅是这样,数据越少,就会变得更加模糊。当然这不是我们想要的结果,但幸运的是我们有了解决的方法。怎么解决,好,请耐心点,我们将会在接下来的部分看到,现在我们必须完成剩下的任务。
我已经说过,我们想要生成的按钮有两种功能,这就意味着它不仅能够生成二维码(已经完成),也能够移除二维码图片和qrcodeImage 属性.所以我们添加剩下的代码到方法中。使得我们更容易仅通过一个按钮来重新创建二维码。
@IBAction func performButtonAction(sender: AnyObject) {
    if qrcodeImage == nil {
        ...

        btnAction.setTitle("Clear", forState: UIControlState.Normal)        
        slider.hidden = false
    }
    else {
        imgQRCode.image = nil
        qrcodeImage = nil
        btnAction.setTitle("Generate", forState: UIControlState.Normal)
    }
}

第一个条件中是创建一个新的二维码,新建一行代码修改按钮标题 “生成”变为“清除”,再加一行代码来实现滑块控件的显示(之前为隐藏状态),在第二个条件中,已经存在的二维码图片必须清除,所以我们做了些适当的操作,注意,最后我们要把按钮标题改回来。
如果你再次运行app你将会注意到你能任意的创建和删除二维码,所以试试吧,不要担心图像模糊,我们几分钟就能将它解决。

修复模糊

这里我们要将成功生成的模糊的二维码图片变得清晰,同时完美的适应视图控件大小。然而,我们必须十分小心谨慎,因为我们不能去改变二维码图片和让它不可以读取。最终二维码里的结果不需保持不变。有一些方法去实现,但是这里我将展示一种简单方法。 当然还有更多的选择,像使用CoreGraphics 库的例子,后面可以自己去查找看看,就我个人而言,我认为我将展示的方法是最快的。
所以,放大二维码而不让它变模糊的的基本想法是改变它的transform 属性,但是,发大图片然后添加到视图中是不可行的,我们将创建另外已经放大过的CIImage图像,然后再分配给视图控件。
现在有一个问题,就是二维码图片改被放大多少。我们知道,二维码图像大小取决于内容和输入校正水平值,所以我们如何能达到最好的效果?实际上,问题的答案并不难找。如果我们把放大的图片的宽度与高度和原始图片宽度与高度进行对比,他们的比例是相等的。在程序上,放大因子的X和Y如下:
let scaleX = imgQRCode.frame.size.width / qrcodeImage.extent().size.width
let scaleY = imgQRCode.frame.size.height / qrcodeImage.extent().size.height
extent()方法返回图片的大小。

所以,根据以上的想法,我们先创建一个自定义方法,命名为displayQRCodeImage()。我特意要去创建一个自定义方法来替代在IBAction方法中添加新的代码,因为这样会更容易去注意我们要实现的功能。
接下来我们实现三个任务:
1,我们指定每一个轴的放大因子(之前显示给你的)
2,我们创建一个新的CIImage 第一个转化的结果(放大图片)
3,我们将转化图片为UIImage 类型最后分配到视图控件中去。
func displayQRCodeImage() {
 let scaleX = imgQRCode.frame.size.width / qrcodeImage.extent().size.width
 let scaleY = imgQRCode.frame.size.height / qrcodeImage.extent().size.height

 let transformedImage = qrcodeImage.imageByApplyingTransform(CGAffineTransformMakeScale(scaleX, scaleY))

imgQRCode.image = UIImage(CIImage: transformedImage)


}
CIImage类的方法imageByApplyingTransform(:)是最主要的实现方法。它实际上通过转化我们现有想要转化的图像去创建并放回一个新的图像。
我们要实现的方法内容是需要让二维码图像清晰避免出现模糊(老实说,所有都是可以写在一行里的)。
现在 ,在测试app之前还有最后一步,我们必须回到performButtonAction(:)方法中来操作。首先,找到下面这一行,并删除整行。
imgQRCode.image = UIImage(CIImage: qrcodeImage)

接下来,仅仅调用新的自定义方法:

@IBAction func performButtonAction(sender: AnyObject) {
    if qrcodeImage == nil {
        ...

        displayQRCodeImage()
    }
    ...
}
我们准备再次测试我们的应用,执行,添加文本,使用生产按钮去创建一个二维码,当你看到下面的截图,这次的输出图片更清晰了,到目前为止没有任何的模糊效果。

不仅仅一种尺寸

现在你已经知道了所有关于生产二维码图片的知识,然而,我并没说完,因为还有最后一件我被问过许多次的事情我想演示。这个问题实际上就是说如何能够动态得到二维码图片大小,而不仅仅固定一种大小和一个视图。
之前一部分你知道我们控制放大二维码图片适应视图控件并且不变模糊。基本的思路是去改变转化的图像并创建新的放大的图像,最终效果就是避免产生模糊效果。这里我们将基于相同的思路,但这次我们不在关注于二维码图像,反而,我们将改变视图空间的大小,通过这样处理,二维码将相应的改变大小。
通过使用底部的滑块控件我们能制作出更多的有趣的实例。如果你还记得,在UI设置中,我们设置滑块最小值为0,最大值为2.默认值设置为1,它代表当前图像的放大倍数(默认的视图不放大也不缩小,所以放大倍数为1)。如果我们想缩小视图,滑块向左滑动,放大视图就向右滑动。当滑到最大值,图像就变为原来的两倍大,这时视图看起来更大。
之前我们定义了下面这个方法:
@IBAction func changeImageViewScale(sender: AnyObject) {

}
滑块每次值改变都会调用这个方法。我们需要改变视图的大小就去要调用这个方法。滑块的值匹配对应的视图的放大值,我们可以直接使用这个值。你可以看到只需要下面一行代码:

@IBAction func changeImageViewScale(sender: AnyObject) {
    imgQRCode.transform = CGAffineTransformMakeScale(CGFloat(slider.value), CGFloat(slider.value))
}

要注意的是滑块的值是个Float类型,CGAffineTransformMakeScale 方法需要CGFloat类型的参数,所以要做相应的转化。
如果你现在运行你的app,你可以看到二维码随着你滑动滑块进行放大和缩小。在不同尺寸中展示二维码这种功能是非常有用的,同事还能避免破坏二维码数据。



最后

目前一切都完成的很顺利,但是还是有些事情困扰着我们。第一个就是当移除二维码的时候底部滑块没有隐藏,任然显示在视图中。你在编辑文本框,并创建了二维码图片后键盘没有隐藏。此外,没有办法在按钮功能改变的时候立即隐藏键盘。
幸运的是解决以上问题只需要在performButtonAction方法中加入两行代码,首先找到如下代码删除它:
slider.hidden = false
然后在该方法末尾加入下面这两行代码:
@IBAction func performButtonAction(sender: AnyObject) {
    ...

    textField.enabled = !textField.enabled
    slider.hidden = !slider.hidden
}

改代码实现的功能是,当你点击按钮时,文本框变为可编辑或者不可编辑,滑块变得隐藏或者显示。这些小细节让我们的的例子变得非常完美,你可以再试试,看看运行的效果是否如你预期的那样。

总结

现在在教程的最后,我真的希望我们创建这个简单应用的每一步将能够帮助你创建你自己的二维码。当你亲自去制作的过程中,不需要花费过多的努力,在整个过程中没有太多的困难。为了得到更好的效果(实际上为了有效果),确保你没有提供大数据作为二维码的资源,如果你那样做了可能你一无所获。
不管怎么说,下次如果你应用中需要用到二维码生成的功能,你可以这样做,因为你已经知道了方法。别急着离开,指导创建二维码给你带了快乐。
作为参考,你可以下载完整的项目在 这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值