作者:AppCoda棍掐,原文鏈接阵赠,原文日期:2016-05-19
譯者:Prayer;校對(duì):numbbbbb鹤啡;定稿:CMB
排著長(zhǎng)隊(duì)等待結(jié)賬的商店惯驼,幫助旅客記錄包裹和航班信息的機(jī)場(chǎng),幫助大型零售商處理大量無(wú)聊的存貨清單递瑰,這些場(chǎng)景非常適合使用條碼掃描器祟牲。此外,條碼掃描器也能幫消費(fèi)者進(jìn)行智能購(gòu)物和產(chǎn)品分類抖部。既然它這么棒说贝,不如我們?cè)?iPhone 上做一個(gè)吧!
幸運(yùn)的是慎颗,對(duì) Apple 開(kāi)發(fā)者來(lái)說(shuō)乡恕,實(shí)現(xiàn)條碼掃描非常容易,蘋(píng)果大法好俯萎!我們會(huì)使用 AV Foundation 來(lái)實(shí)現(xiàn)一個(gè)小巧的 iPhone app傲宜,能夠掃描 CD 上的條碼,獲取專輯的一些重要信息夫啊,并將內(nèi)容輸出到 App 視圖中函卒。能夠?qū)崿F(xiàn)讀取條碼的功能,這非常的酷撇眯,但是我們的野心不止于此报嵌,我們會(huì)對(duì)識(shí)別的條碼內(nèi)容作進(jìn)一步的操作。
我本不該再多啰嗦熊榛,不過(guò)還是友情提醒一下锚国,這個(gè)條碼掃描 app 只有在設(shè)備具有攝像頭時(shí)才能正確工作。記住這一點(diǎn)来候,準(zhǔn)備一臺(tái)有攝像頭的 iOS 設(shè)備跷叉,我們開(kāi)始吧逸雹!
關(guān)于 CDBarcodes
今天我們創(chuàng)建的應(yīng)用叫做 CDBarcodes —— 它還是很智能的营搅。當(dāng)設(shè)備掃描到一個(gè)條碼時(shí)云挟,我們會(huì)將處理后的條碼內(nèi)容發(fā)送給 Discogs 數(shù)據(jù)庫(kù),然后獲得專輯的名稱转质、藝術(shù)家以及發(fā)布年份园欣。Discogs 的數(shù)據(jù)庫(kù)中有大量的音樂(lè)數(shù)據(jù),所以我們基本上能查到所有數(shù)據(jù)休蟹。
從這里下載 CDBarcodes 的 starter project
Discogs
先從 Discogs 開(kāi)始沸枯。首先,我們需要登錄或者注冊(cè)一個(gè) Discogs 賬戶赂弓。登錄之后绑榴,拉到網(wǎng)站的最底端,在 footer 的最左邊邊欄盈魁,點(diǎn)擊 API翔怎。
在 Discogs API 頁(yè)面,點(diǎn)擊左邊欄 Database 中的 Search杨耙。
這個(gè)就是我們將會(huì)用到的 API赤套。我們使用 “title” 和 “year” 參數(shù)來(lái)獲取專輯信息。
現(xiàn)在我們需要將查詢的 URL 保存到我們的 CDBarcodes 中珊膜。在 Constants.swift
文件中容握,將 https://api.discogs.com/database/search?q=
添加到常量 DISCOGS_AUTH_URL
中。
let DISCOGS_AUTH_URL = "https://api.discogs.com/database/search?q="
現(xiàn)在我們可以很方便地在應(yīng)用中使用 DISCOGS_AUTH_URL
獲取查詢 URL车柠。
回到剛才的 Discogs API 網(wǎng)站剔氏。我們需要?jiǎng)?chuàng)建一個(gè)新應(yīng)用,取得 API 的使用資格竹祷。在導(dǎo)航欄中介蛉,網(wǎng)頁(yè)的最頂部,點(diǎn)擊 Create an App溶褪。之后點(diǎn)擊 Create an Application 按鈕币旧。
應(yīng)用名稱的話,輸入 “CDBarcodes + 你的名字”猿妈,或者其他你喜歡的名字吹菱。description 字段可以寫(xiě):
“This is an iOS app that reads barcodes from CDs and displays information about the albums.”
譯注:“這個(gè) iOS 應(yīng)用會(huì)讀取 CD 的條形碼并顯示唱片信息∨碓颍”
最后鳍刷,點(diǎn)擊 Create Application 按鈕。
在最后的結(jié)果頁(yè)面俯抖,我們能夠得到使用條碼來(lái)做一些操作的資格信息输瓜。
拷貝 Consumer Key,粘貼到 Constants.swift
文件的 DISCOGS_KEY
中。再拷貝 Consumer Secret尤揣,粘貼到 Constants.swift
文件的 DISCOGS_SECRET
中搔啊。
同 URL 一樣,現(xiàn)在我們可以在應(yīng)用中很方便地使用這些變量了北戏。
CocoaPods
為了能夠和 Discogs API 通信负芋,我們使用一個(gè)優(yōu)秀的第三方庫(kù)管理工具:CocoaPods。如果想要了解更多關(guān)于 CocoaPods 的信息嗜愈,或者想學(xué)習(xí)如何安裝它旧蛾,可以到它的官網(wǎng)查詢。
有了 CocoaPods 就可以安裝第三方庫(kù)蠕嫁,我們會(huì)使用 Alamofire 來(lái)請(qǐng)求網(wǎng)絡(luò)锨天,使用 SwiftyJSON 來(lái)處理從 Discogs 返回的 JSON 數(shù)據(jù)。
下面我們把這兩個(gè)庫(kù)引入到 CDBarcodes 工程中剃毒!
CocoaPods 安裝好之后绍绘,打開(kāi)終端,進(jìn)入 CDBarcodes 目錄迟赃,初始化 CocoaPods陪拘,命令如下:
bash
cd <your-xcode-project-directory>
pod init
使用 Xcode 打開(kāi) Podfile:
bash
open -a Xcode Podfile
將下面內(nèi)容拷貝到 Podfile 中:
ruby
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
最后,使用下面的命令來(lái)下載 Alamofire 和 SwiftyJSON:
bash
pod install
現(xiàn)在讓我們回到 Xcode 中纤壁!切記要打開(kāi)的是 CDBarcodes.xcworkspace
識(shí)別條碼
AV Foundation 框架提供了識(shí)別條碼的工具左刽。我們來(lái)大概描述一下工作原理。
- AVCaptureSession 會(huì)管理從攝像頭獲取的數(shù)據(jù)——將輸入的數(shù)據(jù)轉(zhuǎn)為可以使用的輸出
- AVCaptureDevice 表示物理設(shè)備和其他屬性酌媒。AVCaptureSession 會(huì)從 AVCaptureDevice 獲取輸入數(shù)據(jù)
- AVCaptureDeviceInput 從設(shè)備中捕獲數(shù)據(jù)
- AVCaptureMetadataOutput 會(huì)向處理數(shù)據(jù)的 delegate 轉(zhuǎn)發(fā)獲得的元數(shù)據(jù)
在 BarcodeReaderViewController.swift
文件中欠痴,首先導(dǎo)入 AVFoundation
import UIKit
import AVFoundation
同時(shí),我們需要遵循 AVCaptureMetadataOutputObjectsDelegate
協(xié)議秒咨。
在 viewDidLoad()
中喇辽,我們要發(fā)動(dòng)條碼掃描引擎。
首先雨席,創(chuàng)建一個(gè) AVCaptureSession
對(duì)象菩咨,然后設(shè)置 AVCaptureDevice
。之后我們將創(chuàng)建一個(gè)輸入對(duì)象(input object)陡厘,然后將其加入到 AVCaptureSession
中抽米。
class BarcodeReaderViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var session: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
// 創(chuàng)建一個(gè) session 對(duì)象
session = AVCaptureSession()
// 設(shè)置 captureDevice.
let videoCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
// 創(chuàng)建 input object.
let videoInput: AVCaptureDeviceInput?
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
// 將 input 加入到 session 中
if (session.canAddInput(videoInput)) {
session.addInput(videoInput)
} else {
scanningNotPossible()
}
如果你的設(shè)備沒(méi)有攝像頭,那就無(wú)法掃描條碼糙置。我們添加了一個(gè)處理失敗場(chǎng)景的方法云茸。如果沒(méi)有攝像頭,會(huì)彈出一個(gè)提示框來(lái)提示用戶谤饭,換一個(gè)有攝像頭的設(shè)備來(lái)掃描 CD 的條碼标捺。
func scanningNotPossible() {
// 告知用戶該設(shè)備無(wú)法進(jìn)行條碼掃描
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()
方法中懊纳,將 input 添加到 session 之后,我們需要?jiǎng)?chuàng)建 AVCaptureMetadataOutput
并把它也添加到 session 中亡容。我們會(huì)將捕獲到的數(shù)據(jù)通過(guò)串行隊(duì)列發(fā)送給 delegate 對(duì)象嗤疯。
下一步需要聲明我們將要掃描的條碼類型。對(duì)我們而言萍倡,我們需要使用 EAN-13 條碼身弊。有意思的是辟汰,我們掃描的條碼并非都是 EAN-13 類型的列敲;一些有可能是 UPC-A 類型,這可能會(huì)造成識(shí)別的問(wèn)題帖汞。
Apple 通過(guò)在前面加上 0 來(lái)將 UPC-A 條碼轉(zhuǎn)換為 EAN-13 條碼戴而。UPC-A 條碼只有 12 位,EAN-13 條碼翩蘸,和你猜測(cè)的一樣所意,是 13 位。這個(gè)自動(dòng)轉(zhuǎn)化特性的好處是催首,我們?cè)谠O(shè)置 metadataObjectTypes
時(shí)扶踊,只要設(shè)置為 AVMetadataObjectTypeEAN13Code
,EAN-13 和 UPC-A 條碼都將會(huì)被識(shí)別郎任。不過(guò)這會(huì)修改條碼秧耗,因此有可能會(huì)在查詢 Discogs 時(shí)出問(wèn)題,后面我們會(huì)處理這個(gè)問(wèn)題舶治。
如果攝像頭有問(wèn)題分井,我們需要使用 scanningNotPossible()
來(lái)告知用戶。
// 創(chuàng)建 output 對(duì)象
let metadataOutput = AVCaptureMetadataOutput()
// 將 output 對(duì)象添加到 session 上
if (session.canAddOutput(metadataOutput)) {
session.addOutput(metadataOutput)
// 通過(guò)串行隊(duì)列霉猛,將捕獲到的數(shù)據(jù)發(fā)送給相應(yīng)的代理
metadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
// 設(shè)置可掃描的條碼類型
metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code]
} else {
scanningNotPossible()
}
我們已經(jīng)擁有了掃描條碼的強(qiáng)大能力尺锚,現(xiàn)在需要做的是預(yù)覽掃描畫(huà)面。使用 AVCaptureVideoPreviewLayer
在整個(gè)屏幕上顯示拍攝到的畫(huà)面惜浅。
然后瘫辩,我們就可以開(kāi)始掃描了。
// 添加 previewLayer 讓其顯示攝像頭拍到的畫(huà)面
?
previewLayer = AVCaptureVideoPreviewLayer(session: session);
previewLayer.frame = view.layer.bounds;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
view.layer.addSublayer(previewLayer);
// 開(kāi)始運(yùn)行 session
session.startRunning()
在 captureOutput:didOutputMetadataObjects:fromConnection
方法中坛悉,我們可以慶祝一下杭朱,因?yàn)閳?zhí)行到該方法就說(shuō)明已經(jīng)識(shí)別了一些信息。
首先吹散,我們需要從 metadataObjects
數(shù)組中取出第一個(gè)對(duì)象弧械,然后將其轉(zhuǎn)化為機(jī)器可以識(shí)別的格式。然后將轉(zhuǎn)換后的 readableCode
作為一個(gè) string 值傳入 barcodeDetected()
方法中空民。
在看 barcodeDetected()
方法之前刃唐,我們需要以震動(dòng)的形式給用戶一些掃描成功的反饋并且關(guān)閉 session(stop the session)羞迷。萬(wàn)一你忘記關(guān)閉了 session,沒(méi)關(guān)系画饥,你的設(shè)備會(huì)一直震動(dòng)衔瓮,直到你關(guān)閉為止。
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
// 從 metadataObjects 數(shù)組中取得第一個(gè)對(duì)象
if let barcodeData = metadataObjects.first {
// 將其轉(zhuǎn)化為機(jī)器可以識(shí)別的格式
let barcodeReadable = barcodeData as? AVMetadataMachineReadableCodeObject;
if let readableCode = barcodeReadable {
// 將 readableCode 作為一個(gè) string 值抖甘,傳入 barcodeDetected() 方法中
barcodeDetected(readableCode.stringValue);
}
// 以震動(dòng)的形式告知用戶热鞍,識(shí)別成功 AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
// 關(guān)閉 session (避免你的設(shè)備一直嗡嗡震動(dòng))
session.stopRunning()
}
}
我們需要在 barcodeDetected()
中做一些操作。第一個(gè)任務(wù)是彈出一個(gè)提示框告知用戶衔彻,我們掃描到了一個(gè)條碼薇宠。然后將掃描到的信息轉(zhuǎn)化為我們需要的內(nèi)容。
必須去掉掃描內(nèi)容中的空格艰额。去掉空格之后澄港,我們需要判斷條碼是 EAN-13 還是 UPC-A 類型。如果是 EAN-13 類型柄沮,不需要額外的操作回梧。如果是 UPC-A 條碼,它被轉(zhuǎn)化為了 EAN-13 類型祖搓,我們需要把它還原成原有的格式狱意。
就像我們之前討論的那樣,蘋(píng)果在 UPC-A 條碼的前頭加上一個(gè) 0 來(lái)將其轉(zhuǎn)換為 EAN-13拯欧,所以我們需要判斷其是否以 0 開(kāi)頭详囤,如果是的話,刪掉它哈扮。如果沒(méi)有這一步纬纪,Discogs 無(wú)法識(shí)別這個(gè)數(shù)字,我們也沒(méi)有辦法得到正確的數(shù)據(jù)滑肉。
拿到處理后的條碼數(shù)據(jù)之后包各,我們將它傳給 DataService.searchAPI()
然后顯示 BarcodeReaderViewController
func barcodeDetected(code: String) {
// 讓用戶知道,我們掃描到了
let alert = UIAlertController(title: "Found a Barcode!", message: code, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Search", style: UIAlertActionStyle.Destructive, handler: { action in
// 去除空格
let trimmedCode = code.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// 判斷是 EAN 還是 UPC?
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)
}
查看 BarcodeReaderViewController.swift
之前靶庙,我們?cè)?viewDidLoad()
后面添加 viewWillAppear()
和 viewWillDisappear()
问畅。在 viewWillAppear()
方法中,我們讓 session 開(kāi)始運(yùn)行六荒。相應(yīng)的护姆,在 viewWillDisappear()
方法中,讓 session 停止運(yùn)行掏击。
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
中卵皂,我們將引入 Alamofire 和 SwiftyJSON。
接下來(lái)砚亭,聲明一些變量來(lái)存儲(chǔ)我們從 Discogs 獲得的原始數(shù)據(jù)灯变。根據(jù) Bionik6 的建議殴玛,我們將使用 private(set)
來(lái)實(shí)現(xiàn)只讀屬性。
然后創(chuàng)建 Alamofire GET 請(qǐng)求添祸。這里通過(guò)解析 JSON 得到專輯的名稱和年份滚粟。我們分別把得到的名稱和年份原始數(shù)據(jù)賦值給 ALBUM_FROM_DISCOGS
和 YEAR_FROM_DISCOGS
,之后會(huì)使用這些變量來(lái)創(chuàng)建專輯對(duì)象刃泌。
現(xiàn)在凡壤,我們從 Discogs 上獲得了數(shù)據(jù),下面要做的就是展示給全世界耙替!好吧亚侠,展示給 AlbumDetailsViewController.swift
就夠了。使用通知的方式來(lái)實(shí)現(xiàn)林艘。
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) {
// 從 Discogs 上獲取專輯數(shù)據(jù)的 URL
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
// 發(fā)送通知盖奈,讓 AlbumDetailsViewController 知道我們得到了數(shù)據(jù)
NSNotificationCenter.defaultCenter().postNotificationName("AlbumNotification", object: nil)
}
}
}
Album 模型
在專輯的數(shù)據(jù)模型 Album.swift
中混坞,需要將專輯模型轉(zhuǎn)化為我們想要的數(shù)據(jù)狐援。這個(gè)模型接受原始的 artistAlbum
和 albumYear
數(shù)據(jù),把它們轉(zhuǎn)換為更加易讀的數(shù)據(jù)究孕。
import Foundation
class Album {
private(set) var album: String!
private(set) var year: String!
init(artistAlbum: String, albumYear: String) {
// 為專輯信息添加一些額外的數(shù)據(jù)
self.album = "Album: \n\(artistAlbum)"
self.year = "Released in: \(albumYear)"
}
}
是時(shí)候秀一波專輯數(shù)據(jù)了啥酱!
在 viewDidLoad()
方法中,設(shè)置 labels 的內(nèi)容厨诸,提示用戶開(kāi)始掃描镶殷。我們需要添加 observer 來(lái)監(jiān)聽(tīng) NSNotification
從而接收通知。同時(shí)需要在 deinit
中移除監(jiān)聽(tīng)者微酬。
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)監(jiān)聽(tīng)到通知的時(shí)候绘趋,setLabels()
方法將會(huì)被調(diào)用。這里我們將使用 DataService.swift
中的原始字符串來(lái)初始化 Album
對(duì)象颗管。然后將 label 中的內(nèi)容設(shè)置為我們想要的 Album
內(nèi)容陷遮。
func setLabels(notification: NSNotification){
// 使用 DataService.swift 中的數(shù)據(jù)初始化 Album 對(duì)象
let albumInfo = Album(artistAlbum: DataService.dataService.ALBUM_FROM_DISCOGS, albumYear: DataService.dataService.YEAR_FROM_DISCOGS)
artistAlbumLabel.text = "\(albumInfo.album)"
yearLabel.text = "\(albumInfo.year)"
}
測(cè)試 CDBarcodes
我們的 app 完成啦!當(dāng)然垦江,我們可以直接從 CD 封面看到專輯名稱帽馋、藝術(shù)家和發(fā)行年份,但是用我們的 app 要有趣得多比吭!為了更好地測(cè)試 CDBarcodes 應(yīng)用绽族,我們需要找一些 CD 和唱片。這樣就有可能同時(shí)遇到 EAN-13 和 UPC-A 條碼衩藤,真正發(fā)揮 app 的威力吧慢。
在 BarcodeReaderViewController
中,注意將相機(jī)對(duì)焦到條碼上赏表。
這里是完成之后的 CDBarcodes 代碼检诗。
總結(jié)
無(wú)論是商務(wù)人士怖喻、購(gòu)物者還是普通人,條碼掃描器都一個(gè)特別有用的工具岁诉。因此锚沸,能夠開(kāi)發(fā)條碼掃描也非常有用。
掃描那部分比較有趣涕癣。在獲得掃描的數(shù)據(jù)之后哗蜈,我們需要對(duì)數(shù)據(jù)做進(jìn)一步操作,例如判斷是 EAN-13 還是 UPC-A 類型坠韩。我們需要找到轉(zhuǎn)化數(shù)據(jù)的正確方式距潘,然后老司機(jī)就上路了。
如果想了解更多內(nèi)容只搁,可以讀取其他的 metadataObjectTypes
和一些新 API音比。唯一的限制就是你的想象力。
本文由 SwiftGG 翻譯組翻譯氢惋,已經(jīng)獲得作者翻譯授權(quán)洞翩,最新文章請(qǐng)?jiān)L問(wèn) http://swift.gg。