【編者按】本文作者為 Matthew Maher,主要手把手地介紹如何用 Swift 構(gòu)建簡單的條形碼檢測器雹洗。文章系 OneAPM 工程師編譯整理。
超市收銀員對貨物進行掃碼卧波,機場內(nèi)錄入行李或檢查乘客时肿,或是在大型零售商的存貨管理等活動中,條形碼掃碼器都是一個簡單而實用的工具港粱。事實上螃成,條形碼掃碼器還幫助消費者實現(xiàn)了智能購物,貨物分類等用途查坪。這次寸宏,我們將為iPhone開發(fā)一個掃碼器。
我們很幸運偿曙,蘋果公司讓條形碼掃描過程的實現(xiàn)變得很簡單氮凝。我們將會深入AV Foundation框架開發(fā)一個簡單的能夠掃描CD條形碼的app,然后獲得專輯的關(guān)鍵信息望忆,最后在app的界面中打印出來罩阵。閱讀條形碼很酷炫也很重要,我們會根據(jù)讀到的條形碼采取進一步的操作启摄。
不用多說稿壁,能掃碼的設(shè)備必須要有一個攝像頭。從這里開始歉备,讓我們拿一個配備有攝像頭的iOS設(shè)備開始干活吧傅是!
簡介 CDbarcodes
我們今天開發(fā)的這個app名叫CDBarcodes——通俗易懂,即條形碼掃描對象是CD。當(dāng)我們的設(shè)備檢測到一個條形碼時落午,會拾取這個貨碼然后發(fā)送到Discogs的數(shù)據(jù)庫谎懦,獲得其專輯名稱肚豺、藝人姓名以及發(fā)布年份溃斋。Discogs的音樂數(shù)據(jù)庫十分強大,因此我們很有可能找到一些實用信息吸申。
下載CDBarcodes的初始項目梗劫。
除了一個不錯的數(shù)據(jù)庫截碴,Discogs還有一個實用的API來幫助查詢梳侨。我們涉及的僅僅是Discogs提供給開發(fā)者的一小部分功能,不過這已經(jīng)足夠使我們的app跑起來了日丹。
Discogs
進入Discogs網(wǎng)站走哺。首先我們必須注冊一個Discogs賬號并登錄。在這之后哲虾,下拉到頁面最底端丙躏。在頁尾最左欄點擊API。
在Discogs的API界面左側(cè)的數(shù)據(jù)庫區(qū)域點擊搜索(Search)晒旅。
這是我們查詢的端點汪诉。我們將會從“title”和“year”這兩個參數(shù)上獲得專輯信息废恋。
現(xiàn)在,我們將這個URL記錄在CDBarcodes中以便后面的查詢扒寄。在Constants.swift
中添加DISCOGS_AUTH_URL
并賦值https://api.discogs.com/database/search?q=
作為常量鱼鼓。
let DISCOGS_KEY = "your-discogs-key"
現(xiàn)在我們能夠在整個app里面通過DISCOGS_AUTH_URL
調(diào)用URL。
回到Discogs的API頁面该编,選擇創(chuàng)建一個新的app迄本,并獲得一些認(rèn)證信息。在頁面頂端的導(dǎo)航欄中上渴,找到“Create an App”岸梨,點擊該按鈕。
在應(yīng)用名稱欄里輸入“CDBarcodes Your Name”曹阔,或是其他合適的名字。描述可以使用下面的文字:
“這是一個iOS應(yīng)用隔披,旨在在讀取CD的條形碼后顯示專輯信息赃份。”
然后,點擊“Create Application”(即創(chuàng)建應(yīng)用)按鈕抓韩。
在結(jié)束頁面纠永,會看到允許我們使用條形碼的認(rèn)證信息。
復(fù)制“Consumer Key”(用戶秘鑰)到Constants.swift
的DISCOGS_KEY
里面谒拴。
有了這個URL尝江,我們可以很方便的在整個CDBarcodes應(yīng)用里使用這些參數(shù)。
CocoaPods
我們使用功能強大的依賴管理器(dependency manager)CocoaPods來與Discogs的API進行交互炭序。有關(guān)CocoaPods的安裝和其他信息,可以參照CocoaPods官網(wǎng)苍日。
經(jīng)由CocoaPods惭聂,在網(wǎng)絡(luò)端我們將會使用Alamofire,并借助SwiftyJSON來處理Discogs返回的JSON相恃。
現(xiàn)在開始在CDBarcodes實戰(zhàn)吧辜纲!
安裝好CocoaPods,打開終端界面拦耐,調(diào)至CDBarcodes耕腾,在Xcode項目中使用下面的代碼初始化CoccoaPods:
cd <your-xcode-project-directory>
pod init
在Xcode里打開Podfile文件:
open -a Xcode Podfile
輸入或是復(fù)制粘貼下面的代碼至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
最后,運行下面的代碼下載Alamofire和SwiftyJSON:
pod install
現(xiàn)在回到Xcode揩魂!注意開發(fā)app時要保持打開CDBarcodes.xcworkspace(工作區(qū))幽邓。
條形碼閱讀器
蘋果的AV Foundation框架提供了我們開發(fā)這個條形碼閱讀器app需要的相關(guān)工具。下面是整個過程中會涉及到的幾個方面:
AVCaptureSession將會處理來自相機的輸入輸出數(shù)據(jù)火脉。
AVCaptureDevice指的是物理設(shè)備及其它的屬性牵舵。AVCaptureSession從AVCaptureDevice這里接受輸入信息。
AVCaptureDeviceInput從輸入設(shè)備獲取輸入數(shù)據(jù)倦挂。
AVCaptureMetadataOutput將元數(shù)據(jù)對象發(fā)送至代理對象(delegate object)處進行處理畸颅。
在BarcodeReaderViewController.swift
里面,我們的第一步操作是導(dǎo)入AVFoundation方援。
import UIKit
import AVFoundation
注意要遵循AVCaptureMetadataOutputObjectsDelegate
没炒。
在viewDidLoad()
,將運行我們的條形碼閱讀引擎犯戏。
首先送火,新建一個AVCaptureSession
對象并設(shè)置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. 設(shè)置captureDevice
let videoCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
// Create input object. 新建輸入設(shè)備
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()
}
如果設(shè)備碰巧沒有攝像頭時,掃描過程將不可能實現(xiàn)呀非。因此坚俗,我們需要一個報錯函數(shù)镜盯。在這里,我們通知用戶尋找一個有相機的iOS設(shè)備以便進行下一步CD條形碼的讀取猖败。
func scanningNotPossible() {
// Let the user know that scanning isn't possible with the current device. 告知用戶掃描現(xiàn)有設(shè)備無法掃描
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()
速缆,在將輸入添加至(session)模塊后,我們接著新建AVCaptureMetadataOutput
并將它添加到模塊中恩闻。我們將捕捉到的數(shù)據(jù)通過一個串行序列的形式發(fā)送給代理對象艺糜。
下一步就是明確我們應(yīng)該掃描的條形碼類型。在這里我們面對的是EAN-13類型的條形碼判呕。有趣的是倦踢,并不是所有的條形碼都是這種類型送滞;有一些將會是UPC-A格式侠草。這可能會導(dǎo)致錯誤出現(xiàn)。
蘋果會自動將UPC-A格式的條形碼前面加一個0后轉(zhuǎn)為EAN-13格式犁嗅。UPC-A格式的條形碼僅僅有12位數(shù)字边涕;而在EAN-13格式的條形碼中則是13位。這個自動轉(zhuǎn)換過程的一個好處是我們可以查詢metadataObjectTypes AVMetadataObjectTypeEAN13Code
褂微,因此兩種格式的條形碼我們就都能讀取了功蜓。需要注意的是這個轉(zhuǎn)換會直接改變條形碼從而誤導(dǎo)Discogs數(shù)據(jù)庫。不過不用擔(dān)心宠蚂,我們馬上就會解決這個問題式撼。
無論如何,在用戶設(shè)備相機有問題時我們就將用戶引導(dǎo)至scanningNotPossible()
函數(shù)求厕。
// 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. 通過串行序列將捕捉到的數(shù)據(jù)發(fā)送至代理對象著隆。
metadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
// Set barcode type for which to scan: EAN-13. 設(shè)置需要掃描的條形碼類型:EAN-13
metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code]
} else {
scanningNotPossible()
}
現(xiàn)在我們就搞定了這個酷炫的功能,拉出來溜溜吧呀癣!我們將使用AVCaptureVideoPreviewLayer
以整個屏幕展示視頻美浦。
最后,我們開始捕捉模塊项栏。
// Add previewLayer and have it show the video data. 添加previewLayer并展示視頻數(shù)據(jù)
previewLayer = AVCaptureVideoPreviewLayer(session: session);
previewLayer.frame = view.layer.bounds;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
view.layer.addSublayer(previewLayer);
// Begin the capture session. 開啟捕捉模塊
session.startRunning()
In captureOutput:didOutputMetadataObjects:fromConnection
, we celebrate, as our barcode reader found something!
通過captureOutput:didOutputMetadataObjects:fromConnection
浦辨,我們的條形碼閱讀器終于讀取到了一些數(shù)據(jù)。
首先沼沈,我們需要使用第一個對象獲得metadataObjects
數(shù)組并將其轉(zhuǎn)換為可機讀代碼流酬。然后,我們將readableCode
字符串發(fā)送至barcodeDetected()
列另。
在進入barcodeDetected()
函數(shù)前芽腾,我們會停止捕捉模塊并給用戶一個震動反饋。如果我們忘了叫停捕捉模塊访递,那么震動也就停不下來了晦嵌!這也是為什么這是一個好案例的原因。
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
// Get the first object from the metadataObjects array. 獲得metadataObjects數(shù)組的第一個對象
if let barcodeData = metadataObjects.first {
// Turn it into machine readable code 轉(zhuǎn)換為可機讀代碼
let barcodeReadable = barcodeData as? AVMetadataMachineReadableCodeObject;
if let readableCode = barcodeReadable {
// Send the barcode as a string to barcodeDetected() 發(fā)送條形碼數(shù)據(jù)
barcodeDetected(readableCode.stringValue);
}
// Vibrate the device to give the user some feedback. 震動反饋
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
// Avoid a very buzzy device. 結(jié)束捕捉模塊
session.stopRunning()
}
}
在barcodeDetected()
函數(shù)里面我們有很多事情要做。第一個任務(wù)是在震動反饋之后惭载,提示用戶我們已經(jīng)發(fā)現(xiàn)了條形碼旱函。然后我們利用找到的數(shù)據(jù)開始干活!
條形代碼中的空格必須移除描滔。在這之后我們需要確認(rèn)條形碼格式是EAN-13還是UPC-A棒妨。如果是EAN-13我們可以直接使用。如果對象是一個UPC-A代碼含长,那么它已經(jīng)被轉(zhuǎn)化為EAN-13格式券腔,我們需要將其轉(zhuǎn)換為原始格式。
如我們前文已經(jīng)討論的那樣拘泞,蘋果設(shè)備在UPC-A格式的條形碼前添加一個0將其轉(zhuǎn)化為EAN-13格式纷纫,因此我們首先確定代碼是以0開頭的。如果是陪腌,我們需要將它移除辱魁。少了這一步,Discogs數(shù)據(jù)庫將不能識別這個數(shù)字诗鸭,我們也就得不到想要的數(shù)據(jù)了染簇。
在獲得清理后的條形碼字符串后,我們將它發(fā)送至DataService.searchAPI()
并彈出BarcodeReaderViewController.swift
强岸。
func barcodeDetected(code: String) {
// Let the user know we've found something. 告知用戶掃描結(jié)果
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() 將UPC發(fā)送至API
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)
}
在離開BarcodeReaderViewController.swift
之前锻弓,在viewDidLoad()
下面,我們添加 viewWillAppear()
和 viewWillDisappear()
函數(shù)蝌箍。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()
}
}
數(shù)據(jù)服務(wù)
在DataService.swift
里十绑,我們首先要導(dǎo)入Alamofire 和 SwiftyJSON聚至。
接著,我們聲明一些變量以便存儲從Discogs返回的原始數(shù)據(jù)本橙。根據(jù)Bionik6的建議扳躬,我們巧妙地使用private(set)
函數(shù)避免了用戶可能導(dǎo)致的阻塞問題。
然后甚亭,建立Alamofire GET請求贷币。在這里JSON會被解析,從而獲得專輯的title(名稱)和year(發(fā)行年份)亏狰。將原始的title和year字符串賦給ALBUM_FROM_DISCOGS
和 YEAR_FROM_DISCOGS
役纹,在后文將會用到它們來初始化我們的專輯。
現(xiàn)在暇唾,我們擁有了來自Discogs的數(shù)據(jù)促脉,我們可以正式開秀了辰斋;隨之我們通知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 使用URL獲得數(shù)據(jù)
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. 通知AlbumDetailsViewController
NSNotificationCenter.defaultCenter().postNotificationName("AlbumNotification", object: nil)
}
}
}
專輯模塊
在專輯模塊Album.swift
中瘸味,我們會處理專輯數(shù)據(jù)以便符合我們的要求宫仗。這個模塊將會獲取原始的artistAlbum
和 albumYear
字符串然后將它們用戶友好化。在AlbumDetailsViewController.swift
我們展示加工后的album
和 year
信息旁仿。
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)"
}
}
專輯展示時間藕夫!
在viewDidLoad()
模塊中,設(shè)置好指向條形碼閱讀器的標(biāo)簽(label)枯冈。然后毅贮,我們需要在NSNotification
添加觀察者(Observer),以便我們已經(jīng)展示的提示能夠集群尘奏。在deinit
中滩褥,我們會移除觀察者(Observer)。
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)
}
當(dāng)通知出現(xiàn)時罪既,setLabels()
函數(shù)將會被調(diào)用铸题。在這里,我們會使用來自DataService.swift
的原始數(shù)據(jù)初始化Album
琢感。標(biāo)簽將會展示加工后的字符串。
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)"
}
測試 CDBarcodes
應(yīng)用搭建完畢探熔,掃一下CD的條形碼我們就能確定專輯的名稱驹针,藝人和發(fā)行年份信息,這很有意思诀艰!為了更好的測試CDBarcodes柬甥,我們可以隨機找一些CD或是黑膠唱片。這樣我們就更有機會同時遇到EAN-13和UPC-A兩種條形碼格式的案例其垄。目前我們兩者都能處理苛蒲!
為了使應(yīng)用順利運行至BarcodeReaderViewController模塊,注意避免閃光以確保相機能捕捉到條形碼信息绿满。
這里是完整代碼的下載鏈接臂外。
結(jié)論
不管是商人,機智的消費者還是一般人士喇颁,這個條形碼閱讀器都很實用漏健。因此,開發(fā)者拿這個案例來練練手是極好的橘霎。
但是我們也看到有趣的僅僅是掃碼部分蔫浆。在獲得數(shù)據(jù)后,我們遇到了一點小問題姐叁,如EAN-13 和 UPC-A格式問題瓦盛。我們找到了解決問題應(yīng)對需求的辦法洗显。
接下來,我們可以探討一些其他的metadataObjectTypes
以及一些新API原环。機會無窮墙懂,經(jīng)驗無價。
本文系 OneAPM 工程師編譯整理扮念。OneAPM Mobile Insight 以真實用戶體驗為度量標(biāo)準(zhǔn)進行 Crash 分析损搬,監(jiān)控網(wǎng)絡(luò)請求及網(wǎng)絡(luò)錯誤,提升用戶留存柜与。訪問 OneAPM 官方網(wǎng)站感受更多應(yīng)用性能優(yōu)化體驗巧勤,想閱讀更多技術(shù)文章,請訪問 OneAPM 官方技術(shù)博客弄匕。
本文轉(zhuǎn)自 OneAPM 官方博客
原文鏈接:http://www.appcoda.com/simple-barcode-reader-app-swift/