見出し画像

【Xcode】【SwiftUi】簡単!バーコード読み取り処理実装

SwiftUiでバーコードの読み取り処理をなるべく簡単に実装してみる。
完成イメージは下記の動画です。

■まずはSwiftUiの画面とバーコード読み取り画面をつなぐViewModelクラスを作成する。
SwiftUiの画面でも使用するためObservableObjectを継承する。
・読み取ったコード文字列、フラグを@Publishedで定義する。
・読み取り時に実行する処理を定義する。
 ここでは読み取り画面を非表示に設定&読み取りフラグをTrueに設定する。

// 読み取り画面とSwiftUi画面をつなぐクラス
class BarCodeViewModel: ObservableObject {
    // コード
    @Published var code: String = "read barcode..."
    // 読み取り画面表示フラグ
    @Published var isShowing: Bool = false
    // 読み取りフラグ
    @Published var isFound: Bool = false

    /// バーコード読み取り時イベント
    func onFound(_ code: String) {
        self.code = code
        self.isShowing = false
        self.isFound = true
    }
}

■イベントクラスを作成する。
・metadataOutputで取得した文字列がnil以外の場合読み取りイベントを実行する。

/// バーコードイベントクラス
class BarCodeReaderDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate {
    // 読み取りイベント
    var onFound: (String) -> Void = { _  in }
    
    // 読み取り設定
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        for metadata in metadataObjects as! [AVMetadataMachineReadableCodeObject] {
            // バーコードの内容が空かどうかの確認
            if metadata.stringValue == nil { continue }
            // 読み取りイベント実行
            onFound(metadata.stringValue!)
        }
    }
}

■バーコード読み取り画面を作成する。UI部分

/// バーコード読み取り画面
class  BarcodeReadrUIView: UIView {
    // カメラプレビューレイヤ
    var previewLayer: AVCaptureVideoPreviewLayer?
    // 映像セッション情報
    var session = AVCaptureSession()
    // バーコードイベントクラス
    weak var delegate: BarCodeReaderDelegate?
    
    //初期化処理
    init(session: AVCaptureSession) {
        super.init(frame: .zero)
        self.session = session
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //レイアウト設定
    override func layoutSubviews() {
        super.layoutSubviews()
        previewLayer?.frame = self.bounds
    }
}

■バーコード読み取り本体を作成する。(UIViewRepresentableで先ほど作成したBarcodeReadrUIViewを表示させる)
・makeUIViewでUIViewの設定とカメラセッションを開始する。
・dismantleUIViewでカメラセッションを停止する。

/// バーコード読み取りクラス
struct BarCodeReader: UIViewRepresentable  {
    //カメラセッション
    private let session = AVCaptureSession()
    //読み取りイベント
    private let delegate = BarCodeReaderDelegate()
    //映像情報
    private let metadataOutput = AVCaptureMetadataOutput()

    /// 読み取りイベント設定
    func found(_ event: @escaping (String) -> Void) -> BarCodeReader {
        //読み取りイベントを設定する
        delegate.onFound = event
        return self
    }

    /// UIView作成処理
    func makeUIView(context: UIViewRepresentableContext<BarCodeReader>) -> BarcodeReadrUIView {
        //バーコード読み取り画面を作成
        let cameraView = BarcodeReadrUIView(session: session)
        //認証チェック
        checkCameraAuthorizationStatus(cameraView)
        return cameraView
    }

    /// UIView削除処理
    static func dismantleUIView(_ uiView: BarcodeReadrUIView, coordinator: ()) {
        //カメラ停止
        uiView.session.stopRunning()
    }
    
    func updateUIView(_ uiView: BarcodeReadrUIView, context: UIViewRepresentableContext<BarCodeReader>) {
    }

    /// 認証チェック
    private func checkCameraAuthorizationStatus(_ uiView: BarcodeReadrUIView) {
        //カメラ認証状態確認
        let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
        if cameraAuthorizationStatus == .authorized {
            //認証されている場合はカメラセットアップ処理実行
            setupCamera(uiView)
        } else {
            print("AVCaptureDevice.requestAccess")
            // カメラへのアクセスを要求
            AVCaptureDevice.requestAccess(for: .video) { granted in
                DispatchQueue.main.sync {
                    if granted {
                        self.setupCamera(uiView)
                    }
                }
            }
        }
    }

    /// カメラセットアップ
    func setupCamera(_ view: BarcodeReadrUIView) {
        // デバイスオブジェクトを生成(ワイドアングルカメラ・ビデオ・背面カメラを指定)
        let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
            mediaType: .video,
            position: .back)

        // デバイスを取得
        let devices = discoverySession.devices
        
        // 該当するデバイスのうち最初に取得したものを利用する
        if let camera = devices.first {
            do {
                // カメラ設定
                let deviceInput = try AVCaptureDeviceInput(device: camera)
                if self.session.canAddInput(deviceInput) {
                    self.session.addInput(deviceInput)

                    // カメラの映像からバーコードを検出するための設定
                    let metadataOutput = AVCaptureMetadataOutput()
                    if self.session.canAddOutput(metadataOutput) {
                        self.session.addOutput(metadataOutput)

                        //Outputイベント設定
                        metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
                        
                        // 読み取りたいコードの種類を指定
                        metadataOutput.metadataObjectTypes = [.ean13]

                        // カメラの映像を画面に表示するためのレイヤーを生成
                        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
                        view.backgroundColor = UIColor.gray
                        previewLayer.videoGravity = .resizeAspectFill
                        view.layer.addSublayer(previewLayer)
                        view.previewLayer = previewLayer
                        
                        // 読み取り開始
                        self.session.startRunning()
                        
                    }
                }
            } catch {
                print("Error occured while creating video device input: \(error)")
            }
        }
    }
}

■最後にSwiftUi側を作成する。
・if文で読み取り前と後で表示を変える。
・BarCodeReader().found(self.viewModel.onFound)によって読み取りイベントを設定する。

struct TestSampleUi: View {
    @ObservedObject var viewModel:BarCodeViewModel = BarCodeViewModel()
    
    var body: some View {
        HStack{
            if(self.viewModel.isFound){
                //番号表示
                Text(self.viewModel.code).foregroundColor(.gray)
                Spacer()
            }else{
                //読み取り
                Button(action: {
                    self.viewModel.isShowing = true
                }) {
                    Image(systemName: "barcode")
                        .resizable()
                        .frame(width: 40, height: 20)
                    Text("Read Barcode")
                }.fullScreenCover(isPresented: self.$viewModel.isShowing) {
                    BarCodeReader()
                        .found(self.viewModel.onFound)
                }
                Spacer()
            }
        }
        .overlay(VStack{Divider().offset(x: 0, y: 15)})
        .padding(.horizontal)
    }
}

■認証設定
プロジェクトファイルの TARGETS > Info > Custom iOS Target Properties に「Privacy - Camera Usage Description」を追加する。

あとはカメラの要求を設定することでとりあえずの読み取りはできます。
ただし、今のままでは読み取るまで戻ることができない問題と、読み取り範囲が画像全体となっているので範囲を絞る必要があります。

動画についている戻るボタンと読み取り範囲の絞り込みを実装したコピペでOK版は下記になります。
※上記にちょっと付け足しただけなので期待はしないでください。

ここから先は

8,198字 / 2ファイル

¥ 500

この記事が気に入ったらチップで応援してみませんか?