123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- import AVFoundation
- import UIKit
- import DcCore
- class QrCodeReaderController: UIViewController {
- weak var delegate: QrCodeReaderDelegate?
- private let captureSession = AVCaptureSession()
-
- private var infoLabelBottomConstraint: NSLayoutConstraint?
- private var infoLabelCenterConstraint: NSLayoutConstraint?
- private lazy var videoPreviewLayer: AVCaptureVideoPreviewLayer = {
- let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
- videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
- return videoPreviewLayer
- }()
- private var infoLabel: UILabel = {
- let label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
- label.text = String.localized("qrscan_hint")
- label.lineBreakMode = .byWordWrapping
- label.numberOfLines = 0
- label.textAlignment = .center
- label.textColor = .white
- label.adjustsFontForContentSizeCategory = true
- label.font = .preferredFont(forTextStyle: .subheadline)
- return label
- }()
- private let supportedCodeTypes = [
- AVMetadataObject.ObjectType.qr
- ]
- // MARK: - lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- self.edgesForExtendedLayout = []
- title = String.localized("qrscan_title")
- self.setupInfoLabel()
- if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
- self.setupQRCodeScanner()
- } else {
- AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] (granted: Bool) in
- guard let self = self else { return }
- if granted {
- self.setupQRCodeScanner()
- } else {
- self.showCameraWarning()
- self.showPermissionAlert()
- }
- })
- }
- }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- startSession()
- }
- override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
- super.viewWillTransition(to: size, with: coordinator)
- coordinator.animate(alongsideTransition: nil, completion: { [weak self] _ in
- DispatchQueue.main.async(execute: {
- self?.updateVideoOrientation()
- })
- })
- }
- override func viewWillDisappear(_ animated: Bool) {
- stopSession()
- }
- // MARK: - setup
-
- private func setupQRCodeScanner() {
- guard let captureDevice = AVCaptureDevice.DiscoverySession.init(
- deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
- mediaType: .video,
- position: .back).devices.first else {
- self.showCameraWarning()
- return
- }
- do {
- let input = try AVCaptureDeviceInput(device: captureDevice)
- self.captureSession.addInput(input)
- let captureMetadataOutput = AVCaptureMetadataOutput()
- self.captureSession.addOutput(captureMetadataOutput)
- captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
- captureMetadataOutput.metadataObjectTypes = self.supportedCodeTypes
- } catch {
- // If any error occurs, simply print it out and don't continue any more.
- self.showCameraWarning()
- return
- }
- view.layer.addSublayer(videoPreviewLayer)
- videoPreviewLayer.frame = view.layer.bounds
- }
-
- private func setupInfoLabel() {
- view.addSubview(infoLabel)
- infoLabel.translatesAutoresizingMaskIntoConstraints = false
- infoLabelBottomConstraint = infoLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10)
- infoLabelCenterConstraint = infoLabel.constraintCenterYTo(view)
- infoLabelBottomConstraint?.isActive = true
- infoLabel.constraintAlignLeadingTo(view, paddingLeading: 5).isActive = true
- infoLabel.constraintAlignTrailingTo(view, paddingTrailing: 5).isActive = true
- view.bringSubviewToFront(infoLabel)
- }
-
- private func showCameraWarning() {
- DispatchQueue.main.async { [weak self] in
- guard let self = self else { return }
- let text = String.localized("chat_camera_unavailable")
- logger.error(text)
- self.infoLabel.textColor = DcColors.defaultTextColor
- self.infoLabel.text = text
- self.infoLabelBottomConstraint?.isActive = false
- self.infoLabelCenterConstraint?.isActive = true
- }
- }
-
- private func showPermissionAlert() {
- DispatchQueue.main.async { [weak self] in
- let alert = UIAlertController(title: String.localized("perm_required_title"),
- message: String.localized("perm_ios_explain_access_to_camera_denied"),
- preferredStyle: .alert)
- if let appSettings = URL(string: UIApplication.openSettingsURLString) {
- alert.addAction(UIAlertAction(title: String.localized("open_settings"), style: .default, handler: { _ in
- UIApplication.shared.open(appSettings, options: [:], completionHandler: nil)}))
- alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .destructive, handler: nil))
- }
- self?.present(alert, animated: true, completion: nil)
- }
- }
-
- private func updateVideoOrientation() {
- guard let connection = videoPreviewLayer.connection else {
- return
- }
- guard connection.isVideoOrientationSupported else {
- return
- }
- let statusBarOrientation = UIApplication.shared.statusBarOrientation
- let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation.videoOrientation ?? .portrait
- if connection.videoOrientation == videoOrientation {
- print("no change to videoOrientation")
- return
- }
- videoPreviewLayer.frame = view.bounds
- connection.videoOrientation = videoOrientation
- videoPreviewLayer.removeAllAnimations()
- }
- // MARK: - actions
- func startSession() {
- #if targetEnvironment(simulator)
- // ignore if run from simulator
- #else
- captureSession.startRunning()
- #endif
- }
-
- func stopSession() {
- captureSession.stopRunning()
- }
- }
- extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
- func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
- if let metadataObj = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
- if supportedCodeTypes.contains(metadataObj.type) {
- if metadataObj.stringValue != nil {
- self.captureSession.stopRunning()
- self.delegate?.handleQrCode(metadataObj.stringValue!)
- }
- }
- }
- }
- }
- extension UIInterfaceOrientation {
- var videoOrientation: AVCaptureVideoOrientation? {
- switch self {
- case .portraitUpsideDown: return .portraitUpsideDown
- case .landscapeRight: return .landscapeRight
- case .landscapeLeft: return .landscapeLeft
- case .portrait: return .portrait
- default: return nil
- }
- }
- }
|