WEBサイト制作・アプリ開発・システム開発・ブランディングデザイン制作に関するご相談はお気軽にご連絡ください。
構想段階からじっくりとヒアリングし、お客様の課題にあわせたアプローチ手法でお客様の“欲しかった”をカタチにしてご提案いたします。
Blog スタッフブログ
[Swift6]Concurrency対応QR読み込み画面のコードスニペット
こんにちは、株式会社MIXシステム開発担当のBloomです。
今回はSwift6で利用できるConcurrencyに対応したQR読み込み画面ViewControllerのコードスニペットを紹介します。
import UIKit
import AVFoundation
protocol CaptureQRDelegate: AnyObject {
func textCaptured(vc: CaptureQRViewController, text: String)
}
@MainActor
class CaptureQRViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
nonisolated(unsafe) var captureSession: AVCaptureSession? = nil
nonisolated(unsafe) var capturedText: String? = nil
nonisolated(unsafe) var previewLayer: AVCaptureVideoPreviewLayer? = nil
@IBOutlet weak var previewView: UIView!
nonisolated(unsafe) weak var delegate: CaptureQRDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let stat = AVCaptureDevice.authorizationStatus(for: .video)
switch stat {
case .authorized:
self.setupCaptureSession()
break
case .denied:
self.showAlert()
break
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
DispatchQueue.main.async {
self.setupCaptureSession()
}
}
else{
// アラートの表示
DispatchQueue.main.async {
self.showAlert()
}
}
})
break
case .restricted:
break
@unknown default:
break
}
}
func setupCaptureSession() {
captureSession = AVCaptureSession()
let device = AVCaptureDevice.default(for: .video)
if device == nil {
self.dismiss(animated: true, completion: {
// 失敗
})
return
}
do {
let input = try AVCaptureDeviceInput.init(device: device!)
captureSession?.addInput(input)
} catch {
self.dismiss(animated: true, completion: {
// 失敗
})
return
}
let output = AVCaptureMetadataOutput.init()
output.setMetadataObjectsDelegate(self, queue: .main)
captureSession?.addOutput(output)
output.metadataObjectTypes = [.qr, .ean13, .ean8, .upce, .code39, .code93, .code128, .code39Mod43, .aztec, .dataMatrix, .itf14, .pdf417]
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
previewLayer!.frame = previewView.bounds
previewLayer!.videoGravity = .resizeAspectFill
if let orientation = self.convertUIOrientation2VideoOrientation(f: { return self.appOrientation() } ) {
previewLayer!.connection?.videoOrientation = orientation
}
previewView.layer.addSublayer(previewLayer!)
DispatchQueue.global().async {
self.captureSession?.startRunning()
}
}
func showAlert() {
DispatchQueue.main.async {
let alert = UIAlertController.init(title: "カメラの認証が必要です", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { (sender:UIAlertAction) in
self.dismiss(animated: true, completion: nil)
}))
}
}
nonisolated func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if self.capturedText != nil {
return
}
for data in metadataObjects {
// 読み込み領域内チェックをしたい場合に追加
/*
let trans = self.previewLayer!.transformedMetadataObject(for: data)
print (trans!.bounds)
let areaFrame = CGRectMake(0, 0, 400, 400)
if !areaFrame.contains(trans!.bounds) {
print (areaFrame, "has not contain", trans!.bounds)
continue
}
*/
if (data is AVMetadataMachineReadableCodeObject) {
let text = (data as! AVMetadataMachineReadableCodeObject).stringValue
self.capturedText = text
}
}
if let capturedText = self.capturedText {
// なんらかの処理
print(capturedText)
DispatchQueue.main.async {
self.delegate?.textCaptured(vc: self, text: capturedText)
}
}
}
func clearCapturedText() {
capturedText = nil
}
// UIDeviceOrientation -> AVCaptureVideoOrientationにConvert
func convertUIOrientation2VideoOrientation(f: () -> UIDeviceOrientation) -> AVCaptureVideoOrientation? {
let v = f()
switch v {
case UIDeviceOrientation.unknown: return nil
default:
return ([
.portrait: .portrait,
.portraitUpsideDown: .portraitUpsideDown,
.landscapeLeft: .landscapeLeft,
.landscapeRight: .landscapeRight
])[v]
}
}
func appOrientation() -> UIDeviceOrientation {
return UIDevice.current.orientation
}
}
Swift6からSwift Concurrencyの仕様が追加されており、AVCaptureMetadataOutputObjectsDelegateの実装に追加で記述が必要になっています。ここではViewControllerに@MainActorを指定し、クラス変数にnonisolated(unsafe)を指定、さらにnonisolatedキーワードをmetadataOutput関数へ追加することで対応しています。