Swiftでバーコードリーダー機能を実装する方法です。
目標
iPhoneのカメラで本の帯の裏に書かれているバーコードを読み込み、13桁の数字のJANコード(isbn13とも言う)を取得する。
実際に実装するコード
以下の6つのステップでご説明します。
- モジュールをimport
- 変数・定数を定義
- セッションを開始する関数を定義・呼び出し
- delegateの設定
- 画面から離れる直前にセッションを止める
- 検出エリアに枠線を表示
それでは順番に見ていきましょう。
1. モジュールをimport
import AVFoundation
まずカメラアプリを導入するために必要なAVFoundationをimportします。
2. 変数・定数を定義
var captureSession : AVCaptureSession?
var videoLayer : AVCaptureVideoPreviewLayer?
var isbn : String?
//検出エリアをカスタマイズする場合のみ使用します
let x: CGFloat = 0.05
let y: CGFloat = 0.4
let width: CGFloat = 0.9
let height: CGFloat = 0.15
次に必要な変数・定数を設定します。
AVCaptureSession
とは
画像や動画といった出力データの管理を行うクラス
AVCaptureVideoPreviewLayer
とは
カメラが取得した映像を画面に表示させるクラス
の意味があります。
またカメラ画面に四角い枠を表示させて、検出エリアを設定したい場合は下4行の定数も書いておきましょう。(そっちの方がユーザービリティは高そう)
3. セッションを開始する関数を定義・呼び出し
func startCapture(){
//画像や動画といった出力データの管理を行うクラス
let session = AVCaptureSession()
//カメラデバイスの管理を行うクラス
guard let device : AVCaptureDevice = AVCaptureDevice.default(for: .video) else {
return
}
//AVCaptureDeviceをAVCaptureSessionに渡すためのクラス
guard let input : AVCaptureInput = try? AVCaptureDeviceInput(device: device) else {
return
}
//inputをセッションに追加
session.addInput(input)
//outputをセッションに追加
let output = AVCaptureMetadataOutput()
session.addOutput(output)
//取得したメタデータを置くAVCaptureMetadataOutputの設定(delegateの設定)
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//取得したメタデータを置くAVCaptureMetadataOutputの設定(何を検出するか JANコードの場合はean8とean13、他にもqrやcode93などがある)
output.metadataObjectTypes = [.ean8, .ean13]
//バーコードの検出エリアの設定(設定しない場合、画面全体が検出エリアになる)
output.rectOfInterest = CGRect(x: y,y: 1-x-width,width: height,height: width)
//セッションを開始
session.startRunning()
//画面上にカメラの映像を表示するためにvideoLayerを作る
let videoLayer = AVCaptureVideoPreviewLayer(session: session)
videoLayer.videoGravity = .resizeAspectFill
videoLayer.frame = self.view.bounds
//videoLayerを最初に宣言した定数に追加する
self.videoLayer = videoLayer
self.view.layer.addSublayer(videoLayer)
//開放用に保持
self.captureSession = session
}
//startCapture関数の呼び出し
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.startCapture()
}
startCapture関数はviewDidAppearで呼び出します。
今回はバーコードを読み取るためにmetadataObjectTypes
でean8
とean13
を設定していますが、他にもqr
やpdf417
といった種類もあります。
詳しくは公式リファレンスをご覧ください。
4. delegateの設定
startCapture関数でAVCaptureMetadataOutputObjectsDelegateの
delegateをselfに設定しているので、必要なmetadataOutput関数を書きます。
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection){
//バーコードが検出されたら呼び出される
for metadataObject in metadataObjects {
guard self.videoLayer?.transformedMetadataObject(for: metadataObject) is AVMetadataMachineReadableCodeObject else { continue }
guard let object = metadataObject as? AVMetadataMachineReadableCodeObject else {
continue
}
guard let detectionString = object.stringValue else {
continue
}
//冒頭の文字や文字数を検出して、正しいコードの場合のみ処理が行われるようにしています
//例えば書籍のJANコード(isbn13)は978から開始される13桁の数字ですので以下のように書いています
if detectionString.starts(with: "978") && detectionString.count == 13 {
self.isbn = detectionString
}
}
//self.isbnが空じゃない時
if self.isbn != nil {
print("取得したコードは\(self.isbn)です!")
//取得し終わったらセッションを止めて空にします
self.captureSession?.stopRunning()
self.captureSession = nil
}
}
5. 画面から離れる直前にセッションを止める
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.captureSession?.stopRunning()
self.captureSession = nil
}
画面から離れるときにはcaptureSessionを止めた上で空にしておきます。
6. 検出エリアに枠線を表示
今の段階では検出エリアをカスタマイズしても、ユーザーからはそのエリアを認識できません。
そのため検出エリアと同じ場所に枠線を表示させて、エリアを分かりやすくする必要があります。
//枠線の表示
let loadArea = UIView()
loadArea.frame = CGRect(x: view.frame.size.width * x, y: view.frame.size.height * y, width: view.frame.size.width * width, height: view.frame.size.height * height)
loadArea.layer.borderColor = UIColor.red.cgColor
loadArea.layer.borderWidth = 4
loadArea.layer.cornerRadius = 6
loadArea.clipsToBounds = true
self.view.addSubview(loadArea)
枠線を表示させるコードはstartCaptureを呼び出した後に書きましょう。
全体コード
最後に今回書いたコードをまとめると以下のようになります。
import UIKit
import AVFoundation
class CaptureViewController : UIViewController {
let detectionArea = UIView()
let x: CGFloat = 0.05
let y: CGFloat = 0.4
let width: CGFloat = 0.9
let height: CGFloat = 0.15
var captureSession : AVCaptureSession?
var videoLayer : AVCaptureVideoPreviewLayer?
var isbn : String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "バーコード読み取り"
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = {
let btn = UIBarButtonItem(title: "完了", style: .plain, target: self, action: #selector(onPressComplete(_:)))
return btn
}()
}
@objc func onPressComplete(_ sender : Any){
self.captureSession?.stopRunning()
self.captureSession = nil
self.dismiss(animated: true, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.captureSession?.stopRunning()
self.captureSession = nil
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.startCapture()
//枠線の表示
loadArea.frame = CGRect(x: view.frame.size.width * x, y: view.frame.size.height * y, width: view.frame.size.width * width, height: view.frame.size.height * height)
loadArea.layer.borderColor = UIColor.red.cgColor
loadArea.layer.borderWidth = 4
loadArea.layer.cornerRadius = 6
loadArea.clipsToBounds = true
self.view.addSubview(loadArea)
}
func startCapture(){
//画像や動画といった出力データの管理を行うクラス
let session = AVCaptureSession()
//カメラデバイスの管理を行うクラス
guard let device : AVCaptureDevice = AVCaptureDevice.default(for: .video) else {
return
}
//AVCaptureDeviceをAVCaptureSessionに渡すためのクラス
guard let input : AVCaptureInput = try? AVCaptureDeviceInput(device: device) else {
return
}
//inputをセッションに追加
session.addInput(input)
//outputをセッションに追加
let output = AVCaptureMetadataOutput()
session.addOutput(output)
//取得したメタデータを置くAVCaptureMetadataOutputの設定(delegateの設定)
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//取得したメタデータを置くAVCaptureMetadataOutputの設定(何を検出するか JANコードの場合はean8とean13、他にもqrやcode93などがある)
output.metadataObjectTypes = [.ean8, .ean13]
//バーコードの検出エリアの設定(設定しない場合、画面全体が検出エリアになる)
output.rectOfInterest = CGRect(x: y,y: 1-x-width,width: height,height: width)
//セッションを開始
session.startRunning()
//画面上にカメラの映像を表示するためにvideoLayerを作る
let videoLayer = AVCaptureVideoPreviewLayer(session: session)
videoLayer.videoGravity = .resizeAspectFill
videoLayer.frame = self.view.bounds
//videoLayerを最初に宣言した定数に追加する
self.videoLayer = videoLayer
self.view.layer.addSublayer(videoLayer)
//開放用に保持
self.captureSession = session
}
}
extension CaptureViewController : AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection){
//バーコードが検出されたら呼び出される
for metadataObject in metadataObjects {
guard self.videoLayer?.transformedMetadataObject(for: metadataObject) is AVMetadataMachineReadableCodeObject else { continue }
guard let object = metadataObject as? AVMetadataMachineReadableCodeObject else {
continue
}
guard let detectionString = object.stringValue else {
continue
}
//冒頭の文字や文字数を検出して、正しいコードの場合のみ処理が行われるようにしています
//例えば書籍のJANコードは978から開始される13桁の数字ですので以下のように書いています
if detectionString.starts(with: "978") && detectionString.count == 13 {
self.isbn = detectionString
}
}
//self.isbnが空じゃない時
if self.isbn != nil {
print("取得したコードは\(self.isbn)です!")
//取得し終わったらセッションを止めて空にします
self.captureSession?.stopRunning()
self.captureSession = nil
}
}
}
参考
https://motty72.hatenablog.com/entry/2019/01/29/233355
https://ichi.pro/swift-de-ba-ko-do-mataha-qr-ko-do-o-yomitoru-hoho-swift-de-no-puroguramingu-18503546520018
https://qiita.com/_asa08_/items/8562fe79ec6528a61b06
https://www.letitride.jp/entry/2019/12/03/125802
Swiftのお役立ち情報