使用 Swift 建立簡單的二維碼掃描應用
排著長隊等待結賬的商店,幫助旅客記錄包裹和航班資訊的機場,幫助大型零售商處理大量無聊的存貨清單,這些場景非常適合使用條碼掃描器。此外,條碼掃描器也能幫消費者進行智慧購物和產品分類。既然它這麼棒,不如我們在 iPhone 上做一個吧!
幸運的是,對 Apple 開發者來說,實現條碼掃描非常容易,蘋果大法好!我們會使用 AV Foundation 來實現一個小巧的 iPhone app,能夠掃描 CD 上的條碼,獲取專輯的一些重要資訊,並將內容輸出到 App 檢視中。能夠實現讀取條碼的功能,這非常的酷,但是我們的野心不止於此,我們會對識別的條碼內容作進一步的操作。
我本不該再多囉嗦,不過還是友情提醒一下,這個條碼掃描 app 只有在裝置具有攝像頭時才能正確工作。記住這一點,準備一臺有攝像頭的 iOS 裝置,我們開始吧!
關於 CDBarcodes
今天我們建立的應用叫做 CDBarcodes —— 它還是很智慧的。當裝置掃描到一個條碼時,我們會將處理後的條碼內容傳送給 Discogs 資料庫,然後獲得專輯的名稱、藝術家以及釋出年份。Discogs 的資料庫中有大量的音樂資料,所以我們基本上能查到所有資料。
從這裡下載 CDBarcodes 的 starter project
Discogs
先從 Discogs 開始。首先,我們需要登入或者註冊一個 Discogs 賬戶。登入之後,拉到網站的最底端,在 footer 的最左邊邊欄,點選 API。
在 Discogs API 頁面,點選左邊欄 Database 中的 Search。
這個就是我們將會用到的 API。我們使用 “title” 和 “year” 引數來獲取專輯資訊。
現在我們需要將查詢的 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="
現在我們可以很方便地在應用中使用 DISCOGS_AUTH_URL
獲取查詢 URL。
回到剛才的 Discogs API 網站。我們需要建立一個新應用,取得 API 的使用資格。在導航欄中,網頁的最頂部,點選 Create an App。之後點選 Create an Application 按鈕。
應用名稱的話,輸入 “CDBarcodes + 你的名字”,或者其他你喜歡的名字。description 欄位可以寫:
“This is an iOS app that reads barcodes from CDs and displays information about the albums.”
譯註:“這個 iOS 應用會讀取 CD 的條形碼並顯示唱片資訊。”
最後,點選 Create Application 按鈕。
在最後的結果頁面,我們能夠得到使用條碼來做一些操作的資格資訊。
拷貝 Consumer Key,貼上到 Constants.swift
檔案的 DISCOGS_KEY
中。再拷貝 Consumer Secret,貼上到Constants.swift
檔案的 DISCOGS_SECRET
中。
同 URL 一樣,現在我們可以在應用中很方便地使用這些變數了。
CocoaPods
為了能夠和 Discogs API 通訊,我們使用一個優秀的第三方庫管理工具:CocoaPods。如果想要了解更多關於 CocoaPods 的資訊,或者想學習如何安裝它,可以到它的官網查詢。
有了 CocoaPods 就可以安裝第三方庫,我們會使用 Alamofire 來請求網路,使用 SwiftyJSON 來處理從 Discogs 返回的 JSON 資料。
下面我們把這兩個庫引入到 CDBarcodes 工程中!
CocoaPods 安裝好之後,開啟終端,進入 CDBarcodes 目錄,初始化 CocoaPods,命令如下:
cd <your-xcode-project-directory> pod init
使用 Xcode 開啟 Podfile:
open -a Xcode Podfile
將下面內容拷貝到 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
現在讓我們回到 Xcode 中!切記要開啟的是 CDBarcodes.xcworkspace
識別條碼
AV Foundation 框架提供了識別條碼的工具。我們來大概描述一下工作原理。
- AVCaptureSession 會管理從攝像頭獲取的資料——將輸入的資料轉為可以使用的輸出
- AVCaptureDevice 表示物理裝置和其他屬性。AVCaptureSession 會從 AVCaptureDevice 獲取輸入資料
- AVCaptureDeviceInput 從裝置中捕獲資料
- AVCaptureMetadataOutput 會向處理資料的 delegate 轉發獲得的後設資料
在 BarcodeReaderViewController.swift
檔案中,首先匯入 AVFoundation
import UIKit import AVFoundation
同時,我們需要遵循 AVCaptureMetadataOutputObjectsDelegate
協議。
在 viewDidLoad()
中,我們要發動條碼掃描引擎。
首先,建立一個 AVCaptureSession
物件,然後設定 AVCaptureDevice
。之後我們將建立一個輸入物件(input object),然後將其加入到 AVCaptureSession
中。
class BarcodeReaderViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { var session: AVCaptureSession! var previewLayer: AVCaptureVideoPreviewLayer! override func viewDidLoad() { super.viewDidLoad() // 建立一個 session 物件 session = AVCaptureSession() // 設定 captureDevice. let videoCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) // 建立 input object. let videoInput: AVCaptureDeviceInput? do { videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice) } catch { return } // 將 input 加入到 session 中 if (session.canAddInput(videoInput)) { session.addInput(videoInput) } else { scanningNotPossible() }
如果你的裝置沒有攝像頭,那就無法掃描條碼。我們新增了一個處理失敗場景的方法。如果沒有攝像頭,會彈出一個提示框來提示使用者,換一個有攝像頭的裝置來掃描 CD 的條碼。
func scanningNotPossible() { // 告知使用者該裝置無法進行條碼掃描 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 之後,我們需要建立 AVCaptureMetadataOutput
並把它也新增到 session 中。我們會將捕獲到的資料通過序列佇列傳送給 delegate 物件。
下一步需要宣告我們將要掃描的條碼型別。對我們而言,我們需要使用 EAN-13 條碼。有意思的是,我們掃描的條碼並非都是 EAN-13 型別的;一些有可能是 UPC-A 型別,這可能會造成識別的問題。
Apple 通過在前面加上 0 來將 UPC-A 條碼轉換為 EAN-13 條碼。UPC-A 條碼只有 12 位,EAN-13 條碼,和你猜測的一樣,是 13 位。這個自動轉化特性的好處是,我們在設定 metadataObjectTypes
時,只要設定為AVMetadataObjectTypeEAN13Code
,EAN-13 和 UPC-A 條碼都將會被識別。不過這會修改條碼,因此有可能會在查詢 Discogs 時出問題,後面我們會處理這個問題。
如果攝像頭有問題,我們需要使用 scanningNotPossible()
來告知使用者。
// 建立 output 物件 let metadataOutput = AVCaptureMetadataOutput() // 將 output 物件新增到 session 上 if (session.canAddOutput(metadataOutput)) { session.addOutput(metadataOutput) // 通過序列佇列,將捕獲到的資料傳送給相應的代理 metadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue()) // 設定可掃描的條碼型別 metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code] } else { scanningNotPossible() }
我們已經擁有了掃描條碼的強大能力,現在需要做的是預覽掃描畫面。使用 AVCaptureVideoPreviewLayer
在整個螢幕上顯示拍攝到的畫面。
然後,我們就可以開始掃描了。
// 新增 previewLayer 讓其顯示攝像頭拍到的畫面 previewLayer = AVCaptureVideoPreviewLayer(session: session); previewLayer.frame = view.layer.bounds; previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; view.layer.addSublayer(previewLayer); // 開始執行 session session.startRunning()
在 captureOutput:didOutputMetadataObjects:fromConnection
方法中,我們可以慶祝一下,因為執行到該方法就說明已經識別了一些資訊。
首先,我們需要從 metadataObjects
陣列中取出第一個物件,然後將其轉化為機器可以識別的格式。然後將轉換後的readableCode
作為一個 string 值傳入 barcodeDetected()
方法中。
在看 barcodeDetected()
方法之前,我們需要以震動的形式給使用者一些掃描成功的反饋並且關閉 session(stop the session)。萬一你忘記關閉了 session,沒關係,你的裝置會一直震動,直到你關閉為止。
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) { // 從 metadataObjects 陣列中取得第一個物件 if let barcodeData = metadataObjects.first { // 將其轉化為機器可以識別的格式 let barcodeReadable = barcodeData as? AVMetadataMachineReadableCodeObject; if let readableCode = barcodeReadable { // 將 readableCode 作為一個 string 值,傳入 barcodeDetected() 方法中 barcodeDetected(readableCode.stringValue); } // 以震動的形式告知使用者,識別成功 AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) // 關閉 session (避免你的裝置一直嗡嗡震動) session.stopRunning() } }
我們需要在 barcodeDetected()
中做一些操作。第一個任務是彈出一個提示框告知使用者,我們掃描到了一個條碼。然後將掃描到的資訊轉化為我們需要的內容。
必須去掉掃描內容中的空格。去掉空格之後,我們需要判斷條碼是 EAN-13 還是 UPC-A 型別。如果是 EAN-13 型別,不需要額外的操作。如果是 UPC-A 條碼,它被轉化為了 EAN-13 型別,我們需要把它還原成原有的格式。
就像我們之前討論的那樣,蘋果在 UPC-A 條碼的前頭加上一個 0 來將其轉換為 EAN-13,所以我們需要判斷其是否以 0 開頭,如果是的話,刪掉它。如果沒有這一步,Discogs 無法識別這個數字,我們也沒有辦法得到正確的資料。
拿到處理後的條碼資料之後,我們將它傳給 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
之前,我們在 viewDidLoad()
後面新增 viewWillAppear()
和viewWillDisappear()
。在 viewWillAppear()
方法中,我們讓 session 開始執行。相應的,在 viewWillDisappear()
方法中,讓 session 停止執行。
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() } }
資料服務
在 DataService.swift
中,我們將引入 Alamofire 和 SwiftyJSON。
接下來,宣告一些變數來儲存我們從 Discogs 獲得的原始資料。根據 Bionik6 的建議,我們將使用 private(set)
來實現只讀屬性。
然後建立 Alamofire GET 請求。這裡通過解析 JSON 得到專輯的名稱和年份。我們分別把得到的名稱和年份原始資料賦值給ALBUM_FROM_DISCOGS
和 YEAR_FROM_DISCOGS
,之後會使用這些變數來建立專輯物件。
現在,我們從 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) { // 從 Discogs 上獲取專輯資料的 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 // 傳送通知,讓 AlbumDetailsViewController 知道我們得到了資料 NSNotificationCenter.defaultCenter().postNotificationName("AlbumNotification", object: nil) } } }
Album 模型
在專輯的資料模型 Album.swift
中,需要將專輯模型轉化為我們想要的資料。這個模型接受原始的 artistAlbum
和albumYear
資料,把它們轉換為更加易讀的資料。
import Foundation class Album { private(set) var album: String! private(set) var year: String! init(artistAlbum: String, albumYear: String) { // 為專輯資訊新增一些額外的資料 self.album = "Album: \n\(artistAlbum)" self.year = "Released in: \(albumYear)" } }
是時候秀一波專輯資料了!
在 viewDidLoad()
方法中,設定 labels 的內容,提示使用者開始掃描。我們需要新增 observer 來監聽 NSNotification
從而接收通知。同時需要在 deinit
中移除監聽者。
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()
方法將會被呼叫。這裡我們將使用 DataService.swift
中的原始字串來初始化Album
物件。然後將 label 中的內容設定為我們想要的 Album
內容。
func setLabels(notification: NSNotification){ // 使用 DataService.swift 中的資料初始化 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
我們的 app 完成啦!當然,我們可以直接從 CD 封面看到專輯名稱、藝術家和發行年份,但是用我們的 app 要有趣得多!為了更好地測試 CDBarcodes 應用,我們需要找一些 CD 和唱片。這樣就有可能同時遇到 EAN-13 和 UPC-A 條碼,真正發揮 app 的威力。
在 BarcodeReaderViewController
中,注意將相機對焦到條碼上。
這裡是完成之後的 CDBarcodes 程式碼。
總結
無論是商務人士、購物者還是普通人,條碼掃描器都一個特別有用的工具。因此,能夠開發條碼掃描也非常有用。
掃描那部分比較有趣。在獲得掃描的資料之後,我們需要對資料做進一步操作,例如判斷是 EAN-13 還是 UPC-A 型別。我們需要找到轉化資料的正確方式,然後老司機就上路了。
如果想了解更多內容,可以讀取其他的 metadataObjectTypes
和一些新 API。唯一的限制就是你的想象力。
相關文章
- 簡單易用的二維碼掃描工具:QR Capture for MacAPTMac
- IOS 使用 ZbarSDK 二維碼掃描iOS
- iOS 使用CIDetector掃描相簿二維碼、原生掃描iOSIDE
- iOS二維碼掃描iOS
- 自定義 React Native 二維碼掃描元件(簡單,易用!)React Native元件
- 簡單的Java二維碼應用Java
- iOS中二維碼掃描iOS
- Swift4如何掃描二維碼瞭解一下Swift
- Android 二維碼掃描和生成二維碼Android
- 掃描二維碼登入思路
- 安卓自定義二維碼掃描安卓
- iOS 掃描二維碼/條形碼iOS
- ubuntu安裝zbar二維碼掃描Ubuntu
- Android實現掃描二維碼Android
- Android二維碼生成與掃描Android
- iOS開發之掃描二維碼iOS
- React Native 實現二維碼掃描React Native
- Android 基於zxing的二維碼掃描功能的簡單實現及優化Android優化
- iOS開發-原生二維碼的掃描和生成iOS
- 使用ionic2開發一個二維碼掃描功能
- ios打包 蒲公英生成二維碼掃描下載iOS
- iOS 掃描二維碼(ZBarSDK)遇到的坑~解決方法iOS
- 最完善,快速的 react-native 二維碼掃描React
- XQRCode 一個非常方便實用的二維碼掃描、解析、生成庫
- WebSocket實現app掃描二維碼登入以及ws應用進行負載均衡?WebAPP負載
- 掃描線及其應用
- 用java做一個簡單的二維碼Java
- 微信小程式掃描普通二維碼開啟小程式的方法微信小程式
- 使用 ABAP 程式碼製作手機能夠掃描的二維碼(QRCode)試讀版
- PHP掃描圖片轉點陣 二維碼轉點陣PHP
- Android----二維碼掃描、生成、相簿識別(16號)Android
- Win10系統怎麼識別掃描二維碼Win10
- Google zxing實現二維碼掃描完美解決方案Go
- PHP生成簡單二維碼PHP
- 二維碼簡單封裝封裝
- TWAIN掃描識別控制元件:Web應用程式的掃描器SDKAI控制元件Web
- 全棧工程師之路-React Native之掃描二維碼全棧工程師React Native
- H5端呼起攝像頭掃描二維碼並解析H5