【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】アプリ内からカメラを起動し写真撮影をする方法

【Swift/AVFoundation】アプリ内からカメラを起動し写真撮影をする方法

今回はカメラでバーコードを読み取り、データとして出力していきます。先に重要となるポイントをまとめておきます。

AVCaptureMetadataOutputクラス

公式リファレンス:AVCaptureMetadataOutput

今回は読み取ったデータをメタデータとして出力したいためOutputにはAVCaptureMetadataOutputクラスを指定します。AVCaptureMetadataOutputでは読み取りエリアをカメラのプレビュー内に明示的に指定することで、対象を検知した場合に読み取ったデータをデリゲートメソッドに転送してくれます。

実際に使用するのは以下のメソッドとプロパティです。

  1. setMetadataObjectsDelegateメソッド
  2. metadataObjectTypesプロパティ
  3. 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プロパティ

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を変更すれば様々なコードにも使い回せると思います。

【Swift/AVFoundation】バーコード読取機能の作り方!JANやISBNコード

今回作成する画面(UIViewControllerクラス)は以下の2つです。

  1. MainViewController:カメラ起動ボタン設置画面(左側)
  2. AudioViewController:バーコード読取カメラ画面(右側)

info.plistにNSCameraUsageDescriptionキーを追加

アプリ内からデバイスのカメラにアクセスするためには「info.plist」に「NSCameraUsageDescription」キーを追加する必要があるのでまずはこの作業から始めます。

【Swift UIKit】アプリ内からカメラで写真を撮影する方法!

「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メソッドを定義してその中でSessionInputsOutputsを記述していきます。ここで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)
    }
}

SessionInputsOutputsの定義方法については以下の記事を参考にしてください。

おすすめ記事:【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!)
        }
    }
}

これで全ての実装が完了しました。シミュレーターではテストできないので実機にビルドして試してみてください。

【Swift/AVFoundation】バーコード読取機能の作り方!JANやISBNコード

バーコードの種類

metadataOutput.metadataObjectTypes = [.ean13]

今回JANコードを読み取るために指定した.ean13は本来ヨーロッパで一般に広く認識されているバーコード「EAN13」を読み取る値です。そのアメリカ版がUPC-A、日本版がJANになります。

  1. UPC-A:アメリカ
  2. EAN:ヨーロッパ
  3. 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!)
        }
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index