Swift4 QRコードの読み取りと生成を行なう
今回、QRCoder
という名前でQRコードを読み取るかつ生成するという簡単なクラスを作ってみました。
主に、使ったLibraryはRxSwift
と SwinJect
です。
Protocolの作成
まずは、コードから
protocol QRCoderDelegate { func qrCoder(_ qrCoder: QRCoder, didDetectQRCode url: URL) }
まず、QRコードの読み取りが終わったことを伝えるdelegate処理を行います。このdelegate処理が伝えられる場所では、urlを受け取りことが可能です。
protocol QRCoderProtocol { func configure(on view: UIView) func startRunning() func stopRunning() func generate(from url: String) -> UIImage? func scanQR(from view: UIImage) -> String? }
上記のようにprotocolを生成するとこのprotocolに準拠するClass or Structは、このメソッドを書かなければエラーが起こります。
下のClass QRCoder
は、QRCoderProtocol に準拠しています。そのため、protocolで書かれたメソッドが全て呼ばれています。
final class QRCoder: NSObject, QRCoderProtocol { //カメラの入出力を行うクラス private let captureSession = AVCaptureSession() //カメラに映る映像を画面に写すクラス private var videoPreviewLayer: AVCaptureVideoPreviewLayer? = nil private var delegate: QRCoderDelegate? init(delegate: QRCoderDelegate?) { self.delegate = delegate } func configure(on view: UIView) { configureSession() addVideoPreviewLayer(on: view) } //このメソッドは、captureSessionでの入出力が行われていない場合に`caputureSession.startRunning`で入出力を開始させることができます。 func startRunning() { guard !captureSession.isRunning else { return } captureSession.startRunning() } //このメソッドは、captureSessionでの入出力が行われている場合に`caputureSession.stopRunning`で入出力を終了させることができます。 func stopRunning() { guard captureSession.isRunning else { return } captureSession.stopRunning() } //ここで、QRコードの生成を行なっています。URLを受けっとてそれをUIImageに変換させています。 func generate(from url: String) -> UIImage? { //string型をURLに直すことができるかをguardで判断している。 guard let _ = URL(string: url) else { return nil } //urlをdataに変え、 "inputCorrectionLevel"として英文字を加えます。 let parameters: [String: Any] = [ "inputMessage": url.data(using: .utf8)!, "inputCorrectionLevel": "H" ] //CIFilterは、主に画像の加工などを行う際に利用されます。引数のnameに行いたい処理を書くことでその加工を行うことができます。 //今回は、QRコードの生成ということで`CIQRCodeGenerator`と書きました。そして、Parameterも引数として渡します。 let filter = CIFilter(name: "CIQRCodeGenerator", withInputParameters: parameters) guard let outputImage = filter?.outputImage else { return nil } //次にCIImage->CgImageに変換させる let scaleImage = outputImage.transformed(by: CGAffineTransform(scaleX: 10, y: 10)) guard let cgImage = CIContext().createCGImage(scaleImage, from: scaleImage.extent) else { return nil } //最後に、cgImageをUIImageに変換させます。 return UIImage(cgImage: cgImage) } }
ところどころで、エラーの際はnilを返しています。ここで、nilを流すことでエラー時のハンドリング処理が行うことができます。 上記のコードを見て見ると、CIImageとCGImage、UIImageと3つのimageが出ています。 その違いについて簡単に紹介して行きたいと思います。 CIImage: CIImageは、主に画像のエフェクトをかけることができる。 Foodieなどのアプリは、Imageを受け取りCGImageに直しているのではないかと思います。
CGImage:画像の切り取りなどを行うことができます。 UIImage: UIImageでは加工などを行うことは出来ず主にViewとして使うことができる。
詳しくは、こちらのQiitam記事を参照ください。
https://qiita.com/jok/items/7c9e384abb29c161f56b
//次にimageから QRコードを読み取りstring型で返すという処理の実装です。 func scanQR(from view: UIImage) -> String? { guard let ciImage = CIImage(image: view) else { return nil } let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil) let features = detector?.features(in: ciImage) guard let castFeatures = features as? [CIQRCodeFeature] else { return nil } for feature in castFeatures { guard feature.messageString != nil else { return nil } return feature.messageString } return nil }
//以下で、QRの読み取りを行なっています。 private extension QRCoder { //携帯のカメラを使って処理を行うのかについて書いています。 //以下では、背面のbuiltInWideAngleCameraでVideoを撮るという処理を行なっています。QRコードはvideoで読み取っているみたいです。 func configureSession() { let deviceDiscoverySession = AVCaptureDevice .DiscoverySession( deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back ) //infp.plistでカメラ機能の利用を許可していなければここでerrorになる仕組みです。 guard let captureDevice = deviceDiscoverySession.devices.first else { fatalError("Failed to get the camera device") } //カメラの出力に対する処理を書いています。deviceで読み取ったものを` captureSession.addInput(input)`でinputしています。 do { let input = try AVCaptureDeviceInput(device: captureDevice) captureSession.addInput(input) } catch let error { fatalError(error.localizedDescription) } //出力に対する処理を書いています。 //AVCaptureMetadataOutputは、指定されたメタデータ検出しその処理のためにデリゲートに転送します。 let captureMetadataOutput = AVCaptureMetadataOutput() captureSession.addOutput(captureMetadataOutput) //ここで、データの受け渡しを行うdelegateを設定します。 captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) //今回行うはQRコードの読み取りなのでqrを指定します。 captureMetadataOutput.metadataObjectTypes = [.qr] //最後にカメラからの映像を`AVCaptureVideoPreviewLayer`でviewに写すことができます。 videoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession) videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill } //上記の関数でもvideoPreviewLayerに対する処理を書きましたが、可読性をあげるため、引数のUIViewを扱う処理は別に書きます。 func addVideoPreviewLayer(on view: UIView) { videoPreviewLayer?.frame = view.layer.bounds //UIViewにaddsubViewするのではなく今回はLayerに.addSublayerというコードを書きます。 //Layerに処理を書くことで、 GPUを使った処理を可能とすることができるからです。 view.layer.addSublayer(videoPreviewLayer!) } }
//AVCaptureMetadataOutputから入ってきた情報に対する処理を書いて行きます。 extension QRCoder: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard metadataObjects.count != 0 else { return } //metadataObjectsがQRコードなのかを判断しています。 guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, metadataObject.type == AVMetadataObject.ObjectType.qr else { return } guard let url = URL(string: metadataObject.stringValue!) else { return } //最後にdelegateを使って処理した値をdelegateで返しています。 delegate?.qrCoder(self, didDetectQRCode: url) } }
参考に↓今回の記事の GitHubです。 github.com