swift语言编写一个简单的条形码扫描APP
原文地址:appcoda
在处理职员在杂货店的收银台排了很长的队伍,在机场帮助检查背包和旅客,或者在主要的食品供应商,协助处理乏味的存货清单过程,条形码扫描是很简单的处理工具。实际上,他们已经用了这个办法来解决消费者在智能购物,图书分类,等其他目的。因此,让我们来制作一个iPhone版本的条形码扫描工具吧!
对我们来说幸运的是,苹果已经制作了条形码扫描的程序,实现它是一件很简单的事情。我们将要研究进入AV Foundation框架的世界,组建APP,用来读取光盘上的条形码,获取相册上的一些重要信息,以及输出这些信息在APP上查看。读取条形码是很酷和重要的,所以要进一步读取我们收到的条形码号码。不言而喻,但是条形码扫描APP只有在带有相机的设备上才能使用。基于这一点,让我们拿起带有摄像头的IOS设备开始我们的教程吧! ========================== (全都是废话……)
关于 CDBarcodes
今天我们将要制作的APP的名字是CDBarcodes,当我们的设备发现了一个条形码,我们将扫描它并且发送到Discogs音乐数据库获取相册名字、在最近一年的受关注的艺术家。Discogs有一个很庞大的记录音乐的数据库,因此我们可以很好的获取我们所需要的信息。现在就开始下载 CDBarcodes,进行我们的教程吧!
令人印象深刻的数据库,Discogs提供有用的API供开发者调用查询。我们只能接触到一小部分Discogs提供给开发者的功能,但是这对于我们来说,足够开发一些有趣的APP了。
Discogs
开始浏览产看Discogs内容,首先,我们需要登录和注册一个Discogs账号。在登录之后,滚动到屏幕底部。在底部部分,左边的列,点击API链接。如下图:
在**Discogs**API选项中,左边的面板,Database部分,点击 Search。如下图:
我们将要从这里获取专辑的音乐信息,通过title
和year
这两个参数。现在我们趁机要记录一个URL地址,在 Constants.swift
中,添加https://api.discogs.com/database/search?q=
作为常量DISCOGS_AUTH_URL
的值。
let DISCOGS_KEY = "your-discogs-key"
现在,在整个APP中用方便的DISCOGS_AUTH_URL来访问我们的搜索RUL,回退Discogs API,创建一个新的APP以及配置一些东西,在顶部页面的导航栏中,点击创建APP。让后点击穿件一个应用程序按钮。如下图:
这里应用名称就输入CDBarcodes,当然你也可以自己适当改下。关于描述,如下:
“This is an iOS app that reads barcodes from CDs and displays information about the albums.”
然后,点击创建应用按钮。
默认生成的屏幕会显示用户条形码的数字证书编号,复制这个唯一的数字号码,粘贴到Constants.swift
文件里面的DISCOGS_KEY
常量值,然后复制用户秘钥到Constants.swift
文件里面的DISCOGS_SECRET
值,正如这个URL,我们方便在CDBarodes
里面访问这些数值。如图:
CocoaPods
为了使用Discogs API
,那么我们通过CocoaPods
这个工具,关于更多Discogs
的信息,可以去看CocoaPods’ site.网站查看。
通过 CocoaPods
集成了Alamofire
和SwiftyJSON
用户访问网络和解析获取到的JSON数据。让我们来开始写这个简单的APP吧。
通过,CocoaPods
建立第三方库,需要打开一个控制台,cd到CDBarcodes
项目,初始化CocoaPods
Xcode项目如下:
cd <your-xcode-project-directory>
pod init
open -a Xcode Podfile
然后粘贴以下代码:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'
use_frameworks!
pod 'Alamofire','~> 3.0'
target ‘CDBarcodes’ do
pod 'SwiftyJSON',:git => 'https://github.com/SwiftyJSON/SwiftyJSON.git'
end
实际上可以直接在Xcode中编辑Podfile
文件,添加以上代码也可以。
最后执行:
pod install
完成建立Alamofire
和SwiftyJSON
第三方库,是不是很简单呢?此时我们要做的就是回到XCode中,确保从CDBarcodes.xcworkspace打开项目。
The Barcode Reader
AV Foundation 框架提供了工具用于我们组建 barcode reader工具,这里有些说明:
* AVCaptureSession
管理相机捕获的数据
* AVCaptureDevice
代表物理设备是AV Foundation框架的一个属性,AVCaptureSession
接受从AVCaptureDevice
获取的输入数据。
* AVCaptureDeviceInput
从输入设备中捕获数据。
* AVCaptureMetadataOutput
通过一个Delegate对象暴露获取的原始数据,也就是二进制数据。
在 BarcodeReaderViewController.swift
,第一步导入 AVFoundation。
import UIKit
import AVFoundation
实现 AVCaptureMetadataOutputObjectsDelegate
代理方法.在viewDidLoad()
,获取读取barcode对象。
首先,创建AVCaptureSession
对象,并且设置AVCaptureDevice
,生成了一个输入对象并添加到AVCaptureSession
对象中。
class BarcodeReaderViewController: UIViewController,AVCaptureMetadataOutputObjectsDelegate {
var session: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
// Create a session object.
session = AVCaptureSession()
// Set the captureDevice.
let videoCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
// Create input object.
let videoInput: AVCaptureDeviceInput?
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
// Add input to the session.
if (session.canAddInput(videoInput)) {
session.addInput(videoInput)
} else {
scanningNotPossible()
}
有可能,设备没有照相机,扫描将不能进行。因此需要判断扫描失败的情况,看,我们可以提示用户说,扫描的设备必须要有照相机。
这部分代码如下:
func scanningNotPossible() {
// Let the user know that scanning isn't possible with the current device.
let alert = UIAlertController(title: "Can't Scan.",message: "Let's try a device equipped with a camera.",preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK",style: .Default,handler: nil))
presentViewController(alert,animated: true,completion: nil)
session = nil
}
回到 viewDidLoad()
方法中,添加了一个输入的会话后,需要创建一个AVCaptureMetadataOutput
输出对象,并要添加到该会话中,我们发送捕获到的数据到代理方法中。
下一步就是设置条形码扫描类型,这里我们需要扫描的条形码是EAN-13
类型的,因此并不是要扫描所有的条形码,比如有些UPC-A
类型的条形码就会存在一些问题。
苹果转换UPC-A
条形码到EAN-13
类型是在最前面添加0,UPC-A
条形码只有12位数,但EAN-13
有13位,好的事情是可以通过MetadataObjectTypes
的AVMetadataObjectTypeEAN13Code
设置,来自动转换,但是这种转换会给 Discogs照成一些问题,不用担心,很快我们会开始处理这个问题,再次,如果相机有问题,还将要发送scanningNotPossible()
消息给用户。
// Create output object. let MetadataOutput = AVCaptureMetadataOutput() // Add output to the session. if (session.canAddOutput(MetadataOutput)) { session.addOutput(MetadataOutput) // Send captured data to the delegate object via a serial queue. MetadataOutput.setMetadataObjectsDelegate(self,queue: dispatch_get_main_queue()) // Set barcode type for which to scan: EAN-13. MetadataOutput.MetadataObjectTypes = [AVMetadataObjectTypeEAN13Code] } else { scanningNotPossible() }
好了,现在需要AVCaptureVideoPreviewLayer
类来全屏展示捕获到的视频内容,最后,我们开始捕获数据:
// 添加预览图层,用来展示捕获到的视频内容,就是条形码了.
previewLayer = AVCaptureVideoPreviewLayer(session: session);
previewLayer.frame = view.layer.bounds;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
view.layer.addSublayer(previewLayer);
// 开始捕获
session.startRunning()
在captureOutput:didOutputMetadataObjects:fromConnection
中,可以发现扫描到的条形码信息。
首先需要获取MetadataObjects
数组里面的第一个元素并转换为机器可读的代码,然后发送readableCode
字符串到barcodeDetected()
,接着需要提示用户会话是否成功,可以通过震动手机,最后不要忘了停止我们的会话。
func captureOutput(captureOutput: AVCaptureOutput!,didOutputMetadataObjects MetadataObjects: [AnyObject]!,fromConnection connection: AVCaptureConnection!) {
// Get the first object from the MetadataObjects array.
if let barcodeData = MetadataObjects.first {
// Turn it into machine readable code
let barcodeReadable = barcodeData as? AVMetadataMachineReadableCodeObject;
if let readableCode = barcodeReadable {
// Send the barcode as a string to barcodeDetected()
barcodeDetected(readableCode.stringValue);
}
// Vibrate the device to give the user some Feedback.
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
// Avoid a very buzzy device.
session.stopRunning()
}
}
在barcodeDetected()
方法中还有些工作需要做,获取到的代码必须要移除空格符号,去掉空格符好之后,确认条形码是EAN-13
还是UPC-A
,如果是后者,还需要转换为EAN-13
类型格式,并返还到最初的状态,也就是没去空格的时候。之前讨论的,苹果处理转换时直接最前面添加0,因此想0是不是开头呢,如果移除掉,没有这一步,Discogs
将不会识别出号码,并获取不到数据呢?
获得最终的字符串后,将它发送到BarcodeReaderViewController.swift
文件的DataService.searchAPI()
方法处理。
func barcodeDetected(code: String) {
// Let the user know we've found something.
let alert = UIAlertController(title: "Found a Barcode!",message: code,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Search",style: UIAlertActionStyle.Destructive,handler: { action in
// Remove the spaces.
let trimmedCode = code.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// EAN or UPC?
// Check for added "0" at beginning of code.
let trimmedCodeString = "\(trimmedCode)"
var trimmedCodeNoZero: String
if trimmedCodeString.hasPrefix("0") && trimmedCodeString.characters.count > 1 {
trimmedCodeNoZero = String(trimmedCodeString.characters.dropFirst())
// Send the doctored UPC to DataService.searchAPI()
DataService.searchAPI(trimmedCodeNoZero)
} else {
// Send the doctored EAN to DataService.searchAPI()
DataService.searchAPI(trimmedCodeString)
}
self.navigationController?.popViewControllerAnimated(true)
}))
self.presentViewController(alert,animated: true,completion: nil)
}
在这之前,必须在viewDidLoad()
下面,继续添加viewWillAppear()
和viewWillDisappear()
两个方法,viewWillAppear()
方法中开始捕获会话的行为,相反viewWillDisappear()
就是结束该会话。
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (session?.running == false) {
session.startRunning()
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if (session?.running == true) {
session.stopRunning()
}
}
The Data Service
在DataService.swift
中,导入Alamofire
和SwiftyJSON
模块。然后,声明一些变量用来存储Discogs
返回的数据,
感谢 Bionik6建议,设置private(set)
用来防止别的类更改这个值。之后,获取网络数据,解析JSON格式,并将获取到的专辑标题和
年份赋值给 ALBUM_FROM_DISCOGS
和YEAR_FROM_DISCOGS
变量。好了,现在我们已经可以获取到数据了,需要将这个消息通知到AlbumDetailsViewController.swift
类去,所以需要发送通知到这个类。
import Foundation
import Alamofire
import SwiftyJSON
class DataService {
static let dataService = DataService()
private(set) var ALBUM_FROM_DISCOGS = ""
private(set) var YEAR_FROM_DISCOGS = ""
static func searchAPI(codeNumber: String) {
// The URL we will use to get out album data from Discogs
let discogsURL = "\(DISCOGS_AUTH_URL)\(codeNumber)&?barcode&key=\(DISCOGS_KEY)&secret=\(DISCOGS_SECRET)"
Alamofire.request(.GET,discogsURL)
.responseJSON { response in
var json = JSON(response.result.value!)
let albumArtistTitle = "\(json["results"][0]["title"])"
let albumYear = "\(json["results"][0]["year"])"
self.dataService.ALBUM_FROM_DISCOGS = albumArtistTitle
self.dataService.YEAR_FROM_DISCOGS = albumYear
// Post a notification to let AlbumDetailsViewController know we have some data.
NSNotificationCenter.defaultCenter().postNotificationName("AlbumNotification",object: nil)
}
}
}
The Album Model
在Album.swift
模型中,共有两个属性分别是,album
和year
,用artistAlbum
和albumYear
两个参数来初始化对象的两个数值,代码如下:
import Foundation
class Album {
private(set) var album: String!
private(set) var year: String!
init(artistAlbum: String,albumYear: String) {
// Add a little extra text to the album information
self.album = "Album: \n\(artistAlbum)"
self.year = "Released in: \(albumYear)"
}
}
Time to Show Album Data!
在viewDidLoad()
中,需要处理获取到的通知,添加观察者用于接收通知,代码如下:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
artistAlbumLabel.text = "Let's scan an album!"
yearLabel.text = ""
NSNotificationCenter.defaultCenter().addObserver(self,selector: #selector(setLabels(_:)),name: "AlbumNotification",object: nil)
}
当通知接收到的时候,setLabels()
会被调用,就会初始化Album
专辑信息通过DataService.swift
数据的改变。
func setLabels(notification: NSNotification){
// Use the data from DataService.swift to initialize the Album.
let albumInfo = Album(artistAlbum: DataService.dataService.ALBUM_FROM_DISCOGS,albumYear: DataService.dataService.YEAR_FROM_DISCOGS)
artistAlbumLabel.text = "\(albumInfo.album)"
yearLabel.text = "\(albumInfo.year)"
}
Testing CDBarcodes
好了,我们已经完成了所有的工作,可以开始测试App了,完整代码下载地址:CDBarcodes,总的来说,原始文章废话有点多,但写的是特别的基础,这点对于新手还是比较不错的。下面还有一些说明,就没翻译了,和技术实现没什么关系!