原文: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)
现在添加一个按钮到视图中,设置其属性如下:
- Title = “Generate”
- Text color = White
- Background color (Hex) = #F39C12
- X = 464
- Y = 66
- Width = 120
- Height = 30
- X = 200
- Y = 200
- Width = 200
- Height = 200
- Minimum value = 0
- Maximum value = 2
- Current value = 1
- Min Track Tint (Hex) = #C0392B
- Thumb Tint (Hex) = #D35400
- Hidden = true (check it)
- X = 189
- Y = 550
- Width = 223
- Height = 31 (it can’t be changed)
接下来,任然是选择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的工作已经结束,让我们来看看如何生成二维码图片。
生成一个二维码
@IBAction func performButtonAction(sender: AnyObject) {
if qrcodeImage == nil {
if textField.text == "" {
return
}
}
}
现在我们来看看如何生成二维码,介绍的时候我说过,创建一个新的CoreImage 过滤器是通过类名位CIQRCodeGenerator的类,指定两个参数并得到输出的图像,即为二维码图像。指定所需的两个参数是:
@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,不管怎么样效果也不错。
@IBAction func performButtonAction(sender: AnyObject) {
if qrcodeImage == nil {
...
imgQRCode.image = UIImage(CIImage: qrcodeImage)
textField.resignFirstResponder()
}
}
所有我们做的是为了将CIImage类型转为UIImage 类型 ,通过调用Textfield的resignFirstResponder()方法来隐藏键盘。
@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)
}
}
第一个条件中是创建一个新的二维码,新建一行代码修改按钮标题 “生成”变为“清除”,再加一行代码来实现滑块控件的显示(之前为隐藏状态),在第二个条件中,已经存在的二维码图片必须清除,所以我们做了些适当的操作,注意,最后我们要把按钮标题改回来。
修复模糊
let scaleX = imgQRCode.frame.size.width / qrcodeImage.extent().size.width
let scaleY = imgQRCode.frame.size.height / qrcodeImage.extent().size.height
extent()方法返回图片的大小。
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(:)是最主要的实现方法。它实际上通过转化我们现有想要转化的图像去创建并放回一个新的图像。
imgQRCode.image = UIImage(CIImage: qrcodeImage)
接下来,仅仅调用新的自定义方法:
@IBAction func performButtonAction(sender: AnyObject) {
if qrcodeImage == nil {
...
displayQRCodeImage()
}
...
}
我们准备再次测试我们的应用,执行,添加文本,使用生产按钮去创建一个二维码,当你看到下面的截图,这次的输出图片更清晰了,到目前为止没有任何的模糊效果。
不仅仅一种尺寸
@IBAction func changeImageViewScale(sender: AnyObject) {
}
滑块每次值改变都会调用这个方法。我们需要改变视图的大小就去要调用这个方法。滑块的值匹配对应的视图的放大值,我们可以直接使用这个值。你可以看到只需要下面一行代码:
@IBAction func changeImageViewScale(sender: AnyObject) {
imgQRCode.transform = CGAffineTransformMakeScale(CGFloat(slider.value), CGFloat(slider.value))
}
要注意的是滑块的值是个Float类型,CGAffineTransformMakeScale 方法需要CGFloat类型的参数,所以要做相应的转化。
最后
目前一切都完成的很顺利,但是还是有些事情困扰着我们。第一个就是当移除二维码的时候底部滑块没有隐藏,任然显示在视图中。你在编辑文本框,并创建了二维码图片后键盘没有隐藏。此外,没有办法在按钮功能改变的时候立即隐藏键盘。slider.hidden = false
然后在该方法末尾加入下面这两行代码:
@IBAction func performButtonAction(sender: AnyObject) {
...
textField.enabled = !textField.enabled
slider.hidden = !slider.hidden
}
改代码实现的功能是,当你点击按钮时,文本框变为可编辑或者不可编辑,滑块变得隐藏或者显示。这些小细节让我们的的例子变得非常完美,你可以再试试,看看运行的效果是否如你预期的那样。