【iOS】バーコードリーダーをSwiftで作ってみた


iOSの`AVFoundation`を使ってバーコードを読み取るやつを作ってみました。

調べて見つけた元ネタはこちらです。

[iOS 7でバーコード・QRコードを読み取る方法と生成する方法（おまけもあるよ） - Qiita ](http://qiita.com/hkato193/items/c36a940c2929a124e416)

上記の記事を参考にさせていただきました！

[@hkato193](http://qiita.com/hkato193)さん、ありがとうございました！

上記の記事は`Objective-C`で書かれているので、それを`Swift`に書き直しました。

## 動画

*2016/06/06 追記*

なんかあれなんで動画作ってみました。

<blockquote class="twitter-video" data-lang="ja"><p lang="ja" dir="ltr">バーコードリーダーの動画作ってみた <a href="https://t.co/cIGtj0f7n1">pic.twitter.com/cIGtj0f7n1</a></p>&mdash; ryokosuge (@ryo_kosuge) <a href="https://twitter.com/ryo_kosuge/status/739745527625965568">2016年6月6日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## ソース

```Swift:ViewController.swift
import UIKit
import AVFoundation

final class ViewController: UIViewController {
    
    @IBOutlet weak var captureView: UIView?
    
    private lazy var captureSession: AVCaptureSession = AVCaptureSession()
    private lazy var captureDevice: AVCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    private lazy var capturePreviewLayer: AVCaptureVideoPreviewLayer = {
        let layer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        return layer
    }()
    
    private var captureInput: AVCaptureInput? = nil
    private lazy var captureOutput: AVCaptureMetadataOutput = {
        let output = AVCaptureMetadataOutput()
        output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
        return output
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view, typically from a nib.
        setupBarcodeCapture()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        capturePreviewLayer.frame = self.captureView?.bounds ?? CGRectZero
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - private
    private func setupBarcodeCapture() {
        do {
            captureInput = try AVCaptureDeviceInput(device: captureDevice)
            captureSession.addInput(captureInput)
            captureSession.addOutput(captureOutput)
            captureOutput.metadataObjectTypes = captureOutput.availableMetadataObjectTypes
            capturePreviewLayer.frame = self.captureView?.bounds ?? CGRectZero
            capturePreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            captureView?.layer.addSublayer(capturePreviewLayer)
            captureSession.startRunning()
        } catch let error as NSError {
            print(error)
        }
    }

    private func convartISBN(value: String) -> String? {
        let v = NSString(string: value).longLongValue
        let prefix: Int64 = Int64(v / 10000000000)
        guard prefix == 978 || prefix == 979 else { return nil }
        let isbn9: Int64 = (v % 10000000000) / 10
        var sum: Int64 = 0
        var tmpISBN = isbn9
        /*
        for var i = 10; i > 0 && tmpISBN > 0; i -= 1 {
            let divisor: Int64 = Int64(pow(10, Double(i - 2)))
            sum += (tmpISBN / divisor) * Int64(i)
            tmpISBN %= divisor
        }
        */
        
        var i = 10
        while i > 0 && tmpISBN > 0 {
            let divisor: Int64 = Int64(pow(10, Double(i - 2)))
            sum += (tmpISBN / divisor) * Int64(i)
            tmpISBN %= divisor
            i -= 1
        }
        
        let checkdigit = 11 - (sum % 11)
        return String(format: "%lld%@", isbn9, (checkdigit == 10) ? "X" : String(format: "%lld", checkdigit % 11))
    }
}

extension ViewController: AVCaptureMetadataOutputObjectsDelegate {
    
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    	self.captureSession.stopRunning()
    	guard let objects = metadataObjects as? [AVMetadataObject] else { return }
    	var detectionString: String? = nil
    	let barcodeTypes = [AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeEAN13Code]
    	for metadataObject in objects {
    		loop: for type in barcodeTypes {
    			guard metadataObject.type == type else { continue }
    			guard self.capturePreviewLayer.transformedMetadataObjectForMetadataObject(metadataObject) is AVMetadataMachineReadableCodeObject else { continue }
    			if let object = metadataObject as? AVMetadataMachineReadableCodeObject {
    				detectionString = object.stringValue
    				break loop
    			}
    		}
    		var text = ""
    		guard let value = detectionString else { continue }
    		text += "読み込んだ値:\t\(value)"
    		text += "\n"
    		guard let isbn = convartISBN(value) else { continue }
    		text += "ISBN:\t\(isbn)"
    		resultTextLabel?.text = text
    		let URLString = String(format: "http://amazon.co.jp/dp/%@", isbn)
    		guard let URL = NSURL(string: URLString) else { continue }
    		UIApplication.sharedApplication().openURL(URL)
    	}
    	self.captureSession.startRunning()
    }
}
```

ソースコードを一部変更しました。

## ちょっと解説

バーコードを読み取って、そこから**ISBN**を取得し、Amazonの商品ページに飛ばしてます。

会社の全社会でお酒を飲みながら作ったので、`convartISBN(_: String)`の部分で`for`文使っているところはご愛嬌でお願いします...。

というかその時、違うループを思い浮かばなかったです...。

最初に苦労したのは`Objective-C`で使う`long long`を`Int64`で表現しているところですかね。

## 終わりに

全社会での発表でこれ作ったらもっと楽にできるのになって思いついて、30分ほどで作れました。

みんなからは技術ってすごいんですねーって感動してもらえました。

エンジニアになってよかったなって思う瞬間でした。

以上です。