使用AVFoundation在Swift 3.0中扫描条形码或二维码

13

我正在按照这篇教程进行操作,并尝试将Swift 2.0的代码转换为Swift 3.0。但是当我启动应用程序时,它却无法正常工作!我的意思是,什么也没有发生!以下是我的代码:

ViewController:

class ViewController: UIViewController ,BarcodeDelegate {

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        let barcodeViewController: BarcodeViewController = segue.destination as! BarcodeViewController
        barcodeViewController.delegate = self

    }



    func barcodeReaded(barcode: String) {
        codeTextView.text = barcode
        print(barcode)
    }

}

条形码VC:

import AVFoundation


protocol BarcodeDelegate {

    func barcodeReaded(barcode: String)
}

class BarcodeViewController: UIViewController,AVCaptureMetadataOutputObjectsDelegate {

    var delegate: BarcodeDelegate?
    var captureSession: AVCaptureSession!
    var code: String?


    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        print("works")

        self.captureSession = AVCaptureSession();
        let videoCaptureDevice: AVCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)

        do {

            let videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)

            if self.captureSession.canAddInput(videoInput) {
                self.captureSession.addInput(videoInput)
            } else {
                print("Could not add video input")
            }

            let metadataOutput = AVCaptureMetadataOutput()
            if self.captureSession.canAddOutput(metadataOutput) {
                self.captureSession.addOutput(metadataOutput)

                metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
                metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypePDF417Code]
            } else {
                print("Could not add metadata output")
            }

            let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
            previewLayer?.frame = self.view.layer.bounds
            self.view.layer .addSublayer(previewLayer!)
            self.captureSession.startRunning()
        } catch let error as NSError {
            print("Error while creating vide input device: \(error.localizedDescription)")
        }



    }



    //I THINK THIS METHOD NOT CALL !
    private func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {

        // This is the delegate'smethod that is called when a code is readed
        for metadata in metadataObjects {
            let readableObject = metadata as! AVMetadataMachineReadableCodeObject
            let code = readableObject.stringValue

            // If the code is not empty the code is ready and we call out delegate to pass the code.
            if  code!.isEmpty {
                print("is empty")

            }else {

                self.captureSession.stopRunning()
                self.dismiss(animated: true, completion: nil)
                self.delegate?.barcodeReaded(barcode: code!)


            }
        }

    }

以下是输出结果:

2016-09-17 18:10:26.000919 BarcodeScaning[2610:674253] [MC] 系统组容器systemgroup.com.apple.configurationprofiles的路径为/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles 2016-09-17 18:10:26.007782 BarcodeScaning[2610:674253] [MC] 读取公共有效用户设置。


查看我的回答,如果您需要完整的代码,我可以在Github上提供。 - Victor Sigler
您可以在以下网址找到第三方条形码扫描器:https://cocoapods.org/pods/MGPBarcodeScanner - Mahendra
5个回答

22

第一步需要声明对任何用户私有数据类型的访问权限,这是iOS 10中的一项新要求。您可以通过向应用程序的 Info.plist 添加使用键和目的字符串来完成此操作。

这是因为如果您正在使用以下框架之一并未声明使用,当首次访问时,您的应用程序将崩溃:

  

联系人、日历、提醒事项、照片、蓝牙共享、麦克风、摄像头、位置、健康、HomeKit、媒体库、运动、通话Kit、语音识别、SiriKit、TV提供商。

为避免崩溃,您需要向 Info.plist 添加建议的键:

enter image description here

然后系统会在请求用户允许访问时显示目的字符串:

enter image description here

如需更多信息,请参阅以下文章:

我对您的 BarcodeViewController 进行了一些修改,以使其正常工作,如下所示:

  

BarcodeViewController

import UIKit
import AVFoundation

protocol BarcodeDelegate {
   func barcodeReaded(barcode: String)
}

class BarcodeViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {

   var delegate: BarcodeDelegate?

   var videoCaptureDevice: AVCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
   var device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
   var output = AVCaptureMetadataOutput()
   var previewLayer: AVCaptureVideoPreviewLayer?

   var captureSession = AVCaptureSession()
   var code: String?

   override func viewDidLoad() {
      super.viewDidLoad()

      self.view.backgroundColor = UIColor.clear
      self.setupCamera()
   }

   private func setupCamera() {

      let input = try? AVCaptureDeviceInput(device: videoCaptureDevice)

      if self.captureSession.canAddInput(input) {
          self.captureSession.addInput(input)
      }

      self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)

      if let videoPreviewLayer = self.previewLayer {
          videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
          videoPreviewLayer.frame = self.view.bounds
          view.layer.addSublayer(videoPreviewLayer)
      }

      let metadataOutput = AVCaptureMetadataOutput()
      if self.captureSession.canAddOutput(metadataOutput) {
          self.captureSession.addOutput(metadataOutput)

          metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
          metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]
      } else {
          print("Could not add metadata output")
      }
   }

   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)

       if (captureSession.isRunning == false) {
          captureSession.startRunning();
       }
   }

   override func viewWillDisappear(_ animated: Bool) {
      super.viewWillDisappear(animated)

      if (captureSession.isRunning == true) {
         captureSession.stopRunning();
      }
   }

   func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
       // This is the delegate's method that is called when a code is read
       for metadata in metadataObjects {
           let readableObject = metadata as! AVMetadataMachineReadableCodeObject
           let code = readableObject.stringValue


           self.dismiss(animated: true, completion: nil)
           self.delegate?.barcodeReaded(barcode: code!)
           print(code!)
       }
   }
}

其中一个重要的点是在viewWillAppear(:)viewWillDisappear(:)方法中声明全局变量并启动和停止captureSession。我认为你之前的代码根本没有调用它,因为它从未进入处理条码的方法。

希望这能对你有所帮助。


你知道如何指定要扫描的区域吗(例如仅限于有限的矩形)? - Lê Khánh Vinh
嗨@VictorSigler。请问您在哪里找到了条形码扫描器实现的文档?我只是想理解您代码中的每一行,非常感谢。 - Pepeng Hapon
2
完美,但是没有填满屏幕,所以我又在viewDidLayoutSubviews()中添加了videoPreviewLayer.frame = self.view.bounds来填充视图,因为我已经从nib设置了Viewcontroller。现在它完美地工作了。谢谢。 - MBH
这个完美地工作了。但是当我得到结果并且QR码仍然在相机界限内时,它会在后台持续扫描同一个QR码。我需要只扫描一次。有可能吗? - Akhil Nair
@VictorSigler 你能帮我吗?我也想通过代码获得条形码图像作为 UIImage,怎么做? - Nikunj Kumbhani
显示剩余5条评论

7
这里是Victor Sigler的答案,已更新为不使用强制解包、使用弱协议、在后台线程执行昂贵代码和其他改进的Swift 4版本。请注意,AVCaptureMetadataOutputObjectsDelegate的方法已更改为
captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)

metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

import UIKit
import AVFoundation

protocol BarcodeDelegate: class {
    func barcodeRead(barcode: String)
}

class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    weak var delegate: BarcodeDelegate?

    var output = AVCaptureMetadataOutput()
    var previewLayer: AVCaptureVideoPreviewLayer!

    var captureSession = AVCaptureSession()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupCamera()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        DispatchQueue.global(qos: .userInitiated).async {
            if !self.captureSession.isRunning {
                self.captureSession.startRunning()
            }
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        DispatchQueue.global(qos: .userInitiated).async {
            if self.captureSession.isRunning {
                self.captureSession.stopRunning()
            }
        }
    }

    fileprivate func setupCamera() {
        guard let device = AVCaptureDevice.default(for: .video),
            let input = try? AVCaptureDeviceInput(device: device) else {
            return
        }

        DispatchQueue.global(qos: .userInitiated).async {
            if self.captureSession.canAddInput(input) {
                self.captureSession.addInput(input)
            }

            let metadataOutput = AVCaptureMetadataOutput()

            if self.captureSession.canAddOutput(metadataOutput) {
                self.captureSession.addOutput(metadataOutput)

                metadataOutput.setMetadataObjectsDelegate(self, queue: .global(qos: .userInitiated))

                if Set([.qr, .ean13]).isSubset(of: metadataOutput.availableMetadataObjectTypes) {
                    metadataOutput.metadataObjectTypes = [.qr, .ean13]
                }
            } else {
                print("Could not add metadata output")
            }

            self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
            self.previewLayer.videoGravity = .resizeAspectFill

            DispatchQueue.main.async {
                self.previewLayer.frame = self.view.bounds
                self.view.layer.addSublayer(self.previewLayer)
            }
        }
    }

    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // This is the delegate's method that is called when a code is read
        for metadata in metadataObjects {
            if let readableObject = metadata as? AVMetadataMachineReadableCodeObject,
                let code = readableObject.stringValue {
                dismiss(animated: true)
                delegate?.barcodeRead(barcode: code)
                print(code)
            }
        }
    }
}

1
关于 AVCaptureMetadataOutputObjectsDelegate 的更改说明确实帮了我很多,谢谢。 - Gary Wright

4

Swift 4中的所有条码类型的条码扫描器

以下是我想分享的有关iOS上条形码扫描的几个想法。

  • 将条码扫描逻辑与视图逻辑分离,
  • 在.plist文件中添加条目
  • 设置exposurePointOfInterestfocusPointOfInterest
  • 使用正确转换的CGRect设置rectOfInterests
  • 设置focusModeexposureMode
  • 在更改相机捕获设置时适当地使用lockForConfiguration锁定captureDevice

在.plist文件中添加条目
在Info.plist文件中添加以下代码以允许您的应用程序访问iPhone相机:

<key>NSCameraUsageDescription</key>
<string>Allow access to camera</string>

设置曝光点和对焦点
exposurePointOfInterestfocusPointOfInterest 可以提高扫描质量,让相机更快地对着屏幕中心进行对焦。

设置兴趣点矩形区域
这个属性可以让相机只在屏幕的一部分进行对焦。这种方式可以更快地扫描代码,仅对屏幕中心呈现的代码进行对焦 - 这对于背景中有其他代码的情况非常有用。

设置对焦模式和曝光模式
应按以下方式设置属性:

device.focusMode = .continuousAutoFocus
device.exposureMode = .continuousAutoExposure

这可以让相机持续聚焦并调整曝光,以便于扫描代码。

演示

你可以在这里找到一个已经实现了这个想法的项目: https://github.com/lukszar/QuickScanner


完美的演示,正是我想要的。非常感谢。 - Hardik Thakkar
@HardikThakkar 很高兴听到这个消息。 - lukszar
优秀的总结,列举了调整扫描仪性能的属性。 - ASD Solutions

0

您需要在Info.plist文件中添加NSCameraUsageDescription才能使其正常工作!

只需在info.plist中添加一行,然后在新创建的行中键入NSCameraUsageDescription并添加一个字符串,用于告知用户为什么需要在您的应用程序中访问相机。

这样就可以解决问题了!


我添加了!但仍然无法工作,相机打开但没有检测到QR码或条形码。 - iOS.Lover
是的,现在我遇到了同样的问题!起初,我只是将提供的整个项目转换为Swift 3语法,并添加了NSCameraUsageDescription - 像魔法一样奏效。 但是,现在在按照教程操作后,我卡在了同样的问题上……还不确定具体原因,但我会继续寻找。 - JTing

0
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
  print("caught QR code")
  for metadata in metadataObjects {
     let readableObject = metadata as! AVMetadataMachineReadableCodeObject
     let code = readableObject.stringValue
     if  code!.isEmpty {
        print("is empty")
     } else {
        self.captureSession.stopRunning()
        self.dismiss(animated: true, completion: nil)
        self.delegate?.gotQRCode(code: code!)
     }
  }
}

看起来在Swift 3中方法的签名有些变化。这是正确的版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接