【Swift/AVFoundation】バーコード読取機能の作り方!JANやISBNコード
この記事からわかること
- AVFoundationでバーコード読み取り機能の作成方法
- JANコードやISBNコードを読み取りデータに変換するには?
- AVCaptureMetadataOutputとは?
- AVCaptureMetadataOutputObjectsDelegateプロトコルのmetadataOutput(_:didOutput:from:)メソッドの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
参考文献:AVCamBarcode: Detecting barcodes and faces
Swiftでバーコード読取機能を実装する方法
Swiftでバーコード読取機能を実装するにはカメラ機能を自由にカスタマイズして作成できるAVFoundation
フレームワークを使用します。AVFoundation
フレームワークの基本的な使用方法が分からない方は以下の記事を参考にしてください。
おすすめ記事:【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法
今回はカメラでバーコードを読み取り、データとして出力していきます。先に重要となるポイントをまとめておきます。
- AVCaptureMetadataOutput
- AVCaptureMetadataOutputObjectsDelegate
AVCaptureMetadataOutputクラス
公式リファレンス:AVCaptureMetadataOutput
今回は読み取ったデータをメタデータとして出力したいためOutputにはAVCaptureMetadataOutput
クラスを指定します。AVCaptureMetadataOutputでは読み取りエリアをカメラのプレビュー内に明示的に指定することで、対象を検知した場合に読み取ったデータをデリゲートメソッドに転送してくれます。
実際に使用するのは以下のメソッドとプロパティです。
- setMetadataObjectsDelegateメソッド
- metadataObjectTypesプロパティ
- rectOfInterestプロパティ
setMetadataObjectsDelegateメソッド
setMetadataObjectsDelegate
はコールバックで呼び出されるデリゲートクラスとキューを設定するメソッドです。引数にはAVCaptureMetadataOutputObjectsDelegate
に準拠したクラスとシリアル(直列)のキューを渡します。
// デリゲートクラスとキューを設定
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
おすすめ記事:【Swift】DispatchQueueの使い方!GCDで非同期と遅延処理
metadataObjectTypesプロパティ
公式リファレンス:metadataObjectTypesプロパティ
metadataObjectTypes
は処理させたいメタデータの種類をAVMetadataObject.ObjectType型の配列形式で指定するプロパティです。
メタデータオブジェクトとして認識できるのはバーコードやQRコード、人や犬猫の体、顔などが用意されています。さらにバーコードではその種類によって細かく用意されているので使用したい種類に合わせた値を指定する必要があります。
// メタデータの種類を指定
metadataOutput.metadataObjectTypes = [.code128,.code39,.ean13,.itf14]
rectOfInterestプロパティ
rectOfInterest
はメタデータを読み込むための検索領域を定義するプロパティです。CGRect型(長方形)で領域を定義し、その中にバーコードなどが入った場合に読み込みを開始します。
デフォルト値は(0.0, 0.0, 1.0, 1.0)
となっています。CGRect
の以下のイニシャライザを使用してCGFloat型(Double)で指定すると分かりやすいです。
おすすめ記事:【Swift】CGRectの使い方!Core Graphicsとは?
init(
x: CGFloat,
y: CGFloat,
width: CGFloat,
height: CGFloat
)
検索領域を自身の好きなサイズと位置にすれば良いですが適応させるデバイスの向きが縦ではなくて横のようなので注意が必要です。以下の記事に詳しくまとめられていたのでリンクを貼っておきます。
参考文献:Qiita:iOSのバーコードリーダーで読み取り範囲を設定する
// 読取エリアの設定
let x: CGFloat = 0.1
let y: CGFloat = 0.4
let width: CGFloat = 0.8
let height: CGFloat = 0.2
metadataOutput.rectOfInterest = CGRect(x: y, y: 1 - x - width, width: height, height: width)
AVCaptureMetadataOutputObjectsDelegate
公式リファレンス:AVCaptureMetadataOutputObjectsDelegate
AVCaptureMetadataOutputObjectsDelegate
は読み込んだメタデータを取得するためのデリゲートプロトコルです。このプロトコルを任意のUIViewControllerに準拠させることでデリゲートメソッドからメタデータを取得できます。
protocol AVCaptureMetadataOutputObjectsDelegate {
optional func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
)
}
用意されているデリゲートメソッドはmetadataOutput(_:didOutput:from:)
です。第一引数から対象のAVCaptureMetadataOutputオブジェクトに、第二引数から配列形式に格納されたメタデータを参照できます。
JANコードを読み取る
ここからはJANコードを読み取る機能を実装していきます。指定しているAVMetadataObject.ObjectType
を変更すれば様々なコードにも使い回せると思います。
今回作成する画面(UIViewControllerクラス)は以下の2つです。
- MainViewController:カメラ起動ボタン設置画面(左側)
- AudioViewController:バーコード読取カメラ画面(右側)
info.plistにNSCameraUsageDescriptionキーを追加
アプリ内からデバイスのカメラにアクセスするためには「info.plist」に「NSCameraUsageDescription」キーを追加する必要があるのでまずはこの作業から始めます。
「info.plist」を開いたらKeyにNSCameraUsageDescription
と入力し、Valueにはカメラ使用する旨を記載しておきます。自動でPrivacy - Camera Usage Description
に変換されます。
カメラ起動ボタン設置画面
まずはバーコード読み取り画面を呼び出すボタンを設置する画面を準備します。ここは特に難しいこともないのでコードだけ載せておきます。
import UIKit
import Photos
import AVFoundation
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.backgroundColor = .orange
button.frame = CGRect(x: UIScreen.main.bounds.width/3, y: 200, width: UIScreen.main.bounds.width/3, height: 50)
button.setTitle("カメラ起動", for: .normal)
button.addTarget(self, action: #selector(tapedAudio), for: .touchUpInside)
view.addSubview(button)
allowedRequestStatus()
}
// MARK: - ボタンアクション
@objc func tapedAudio(sender: UIButton) {
self.present(AudioViewController(), animated: true, completion: nil)
}
// MARK: - カメラ利用の承認申請アラート表示メソッド
func allowedRequestStatus() -> Bool{
var avState = false
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
avState = true
break
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
DispatchQueue.main.async {
avState = granted
}
})
default:
avState = false
break
}
return avState
}
}
ここでやっていることは以下の通りです。
- カメラ起動ボタンの設置
- カメラ利用の承認申請アラート表示メソッド
バーコード読取カメラ画面
続いて肝となるバーコード読取機能を作っていきます。まずは必要となるプロパティとメソッドを定義します。
import UIKit
import Photos
import AVFoundation
class AudioViewController: UIViewController {
let photoView:UIImageView! = UIImageView() // カメラ画面用
let codeLabel:UILabel! = UILabel() // 読取結果表示用
// MARK: - AVFoundation
var avSession: AVCaptureSession!
var avInput: AVCaptureDeviceInput!
var avOutput: AVCapturePhotoOutput!
override func viewDidLoad() {
super.viewDidLoad()
photoView.frame = CGRect(x: 0 ,y:0, width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.height)
self.view.addSubview(photoView)
if allowedStatus() {
setupAVCapture()
}
}
// MARK: - 承認状態識別 カメラ
func allowedStatus() -> Bool{
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
return true
}else{
return false
}
}
// MARK: - カメラ画面のセットアップ
func setupAVCapture() {
}
}
Session、Inputs、Outputsの構築
AVFoundation
フレームワークを使用してカメラ機能を使用するためにsetupAVCapture
メソッドを定義してその中でSession
とInputs
、Outputs
を記述していきます。ここでOutputsにAVCaptureMetadataOutput
クラスを使用します。
func setupAVCapture() {
self.avSession = AVCaptureSession()
guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }
do {
let deviceInput = try AVCaptureDeviceInput(device: videoDevice)
if self.avSession.canAddInput(deviceInput) {
self.avSession.addInput(deviceInput)
self.avInput = deviceInput
// バーコード読取用Outputの指定
let metadataOutput = AVCaptureMetadataOutput()
if self.avSession.canAddOutput(metadataOutput) {
self.avSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// バーコードの種類を指定 ここを追加もしくは変更すれば色々できます
metadataOutput.metadataObjectTypes = [.ean13]
// 読取エリアの設定
let x: CGFloat = 0.1
let y: CGFloat = 0.4
let width: CGFloat = 0.8
let height: CGFloat = 0.2
metadataOutput.rectOfInterest = CGRect(x: y, y: 1 - x - width, width: height, height: width)
let previewLayer = AVCaptureVideoPreviewLayer(session: self.avSession)
previewLayer.frame = self.photoView.bounds
previewLayer.videoGravity = .resizeAspectFill
self.view.layer.addSublayer(previewLayer)
// 読取エリアを可視化
let readingArea = UIView()
readingArea.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)
readingArea.layer.borderWidth = 2
readingArea.layer.borderColor = UIColor.orange.cgColor
view.addSubview(readingArea)
// 結果表示
codeLabel.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 120, width: UIScreen.main.bounds.width, height: 60)
codeLabel.backgroundColor = UIColor.gray
codeLabel.textAlignment = .center
codeLabel.text = "placeholder"
self.view.addSubview(codeLabel)
self.avSession.startRunning()
}
}
} catch {
print(error.localizedDescription)
}
}
Session
とInputs
、Outputs
の定義方法については以下の記事を参考にしてください。
おすすめ記事:【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法
デリゲートの実装
最後にAudioViewController
を拡張してAVCaptureMetadataOutputObjectsDelegate
プロトコルに準拠させ、metadataOutput(_:didOutput:from:)
メソッド内に取得後の処理を実装します。
extension AudioViewController:AVCaptureMetadataOutputObjectsDelegate{
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
for metadata in metadataObjects as! [AVMetadataMachineReadableCodeObject] {
if metadata.stringValue == nil { continue }
codeLabel.text = metadata.stringValue!
print(metadata.type)
print(metadata.stringValue!)
}
}
}
これで全ての実装が完了しました。シミュレーターではテストできないので実機にビルドして試してみてください。
バーコードの種類
metadataOutput.metadataObjectTypes = [.ean13]
今回JANコードを読み取るために指定した.ean13
は本来ヨーロッパで一般に広く認識されているバーコード「EAN13」を読み取る値です。そのアメリカ版がUPC-A、日本版がJANになります。
- UPC-A:アメリカ
- EAN:ヨーロッパ
- JAN:日本
今回は明示的に「EAN13」を指定しましたが、この指定でJANコードも同様に読み取ることができます。
書籍のISBNコードも読み取れる
またさらに.ean13
の指定で書籍に付与されているISBNコードも読み取ることが可能でした。
ISBNコードとは「International Standard Book Number:国際標準図書番号」の略称で書籍を一意に識別するために振られる識別番号です。
全体のコード
import UIKit
import Photos
import AVFoundation
class AudioViewController: UIViewController {
let photoView:UIImageView! = UIImageView() // カメラ画面用
let codeLabel:UILabel! = UILabel() // 読取結果表示用
// MARK: - AVFoundation
var avSession: AVCaptureSession!
var avInput: AVCaptureDeviceInput!
var avOutput: AVCapturePhotoOutput!
override func viewDidLoad() {
super.viewDidLoad()
photoView.frame = CGRect(x: 0 ,y:0, width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.height)
self.view.addSubview(photoView)
if allowedStatus() {
setupAVCapture()
}
}
// MARK: - 承認状態識別 カメラ && フォトライブラリ
func allowedStatus() -> Bool{
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
return true
}else{
return false
}
}
func setupAVCapture() {
self.avSession = AVCaptureSession()
guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }
do {
let deviceInput = try AVCaptureDeviceInput(device: videoDevice)
if self.avSession.canAddInput(deviceInput) {
self.avSession.addInput(deviceInput)
self.avInput = deviceInput
// バーコード読取用Outputの指定
let metadataOutput = AVCaptureMetadataOutput()
if self.avSession.canAddOutput(metadataOutput) {
self.avSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// バーコードの種類を指定
metadataOutput.metadataObjectTypes = [.ean13]
// 読取エリアの設定
let x: CGFloat = 0.1
let y: CGFloat = 0.4
let width: CGFloat = 0.8
let height: CGFloat = 0.2
metadataOutput.rectOfInterest = CGRect(x: y, y: 1 - x - width, width: height, height: width)
let previewLayer = AVCaptureVideoPreviewLayer(session: self.avSession)
previewLayer.frame = self.photoView.bounds
previewLayer.videoGravity = .resizeAspectFill
self.view.layer.addSublayer(previewLayer)
// 読取エリアを可視化
let readingArea = UIView()
readingArea.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)
readingArea.layer.borderWidth = 2
readingArea.layer.borderColor = UIColor.orange.cgColor
view.addSubview(readingArea)
// 結果表示
codeLabel.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 120, width: UIScreen.main.bounds.width, height: 60)
codeLabel.backgroundColor = UIColor.gray
codeLabel.textAlignment = .center
codeLabel.text = "placeholder"
self.view.addSubview(codeLabel)
self.avSession.startRunning()
}
}
} catch {
print(error.localizedDescription)
}
}
}
extension AudioViewController:AVCaptureMetadataOutputObjectsDelegate{
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
for metadata in metadataObjects as! [AVMetadataMachineReadableCodeObject] {
if metadata.stringValue == nil { continue }
codeLabel.text = metadata.stringValue!
print(metadata.type)
print(metadata.stringValue!)
}
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。