今天要介绍的是iOS7.0后AVFoundation框架提供的原生二维码扫描(非针对新手)
- 首先导入AVFoundation 框架。
- 我们要编写一个QRCode扫描管理对象MMBCaptureManager。它放出的delegate
@objc protocol QISCaptureManagerDelegate : NSObjectProtocol {
@objc optional func didChangeAccessCameraState(isGranted:Bool);
@objc optional func didOutputDecodeStringValue(stringValue:NSString);
@objc optional func didDecodeUnmatchType(codeType:NSString);
}
会把扫描结果以子线程的形式进行回调。
一开始需要请求相机的授权
接下来是一堆配置,生成AVCaptureSession,挂载output和input,videoPreViewLayer对象。AVCaptureDevice用设置是否自动对焦,闪光灯。在AVCaptureMetadataOutput里,我们设置metadata的类型为AVMetadataObjectTypeQRCode,值得一提的属性是AVCaptureMetadataOutput的rectOfInterest属性,在苹果的文档中,这个值以UV坐标的形式表示相机捕捉metaObject的区域。我这里是取 手机屏幕宽度-30*2 的 正方形区域,并把这个区域居中。
videoPreViewLayer则是渲染到屏幕的输出结果,是官方实现的比较便利把图像流输出到layer的方法,也可以自己设置OpenglContext去把流渲染,不过比较麻烦。
// #pragma MARK: - 授权
func authCaputre()
{
let authStatus:AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
if(authStatus == .notDetermined)
{
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: {
granded in
if(granded){
self.setupCature()
self.didChangeAccessCameraState(isGranted: true);
}
else
{
self.didChangeAccessCameraState(isGranted: false);
}
})
}
else if(authStatus == .authorized || authStatus == .restricted)
{
self.setupCature()
}
else if(authStatus == .denied)
{
(self.rootViewController as! MMBScanCodeViewController).alertCameraAuth()
}
}```
// MARK: 开始捕捉
func setupCature()
{
// MARK : 配置
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)!
do
{
try captureDevice.lockForConfiguration()
}
catch { }
//设置自动对焦
if (captureDevice.isFocusModeSupported(.continuousAutoFocus))
{
captureDevice.focusPointOfInterest = CGPoint(x:0.5 , y:0.5)
captureDevice.focusMode = .continuousAutoFocus
}
if(captureDevice.hasTorch && captureDevice.isTorchModeSupported(.auto))
{
captureDevice.torchMode = .auto
}
captureDevice.videoZoomFactor = captureDevice.activeFormat.videoZoomFactorUpscaleThreshold
captureDevice.unlockForConfiguration()
// MARK: 设置session
var isHighPresent : Bool = false
let idiom = UIDevice.current.userInterfaceIdiom
if idiom == .pad || CGFloat(WindowHeight) < 480 + 1
{ isHighPresent = true}
if isHighPresent {
captureSession.sessionPreset = AVCaptureSessionPresetHigh
}
else if captureSession.canSetSessionPreset(AVCaptureSessionPreset1920x1080)
{
captureSession.sessionPreset = AVCaptureSessionPreset1920x1080
}
let captureInput = try? AVCaptureDeviceInput.init(device: captureDevice)
if captureInput == nil { debugPrint("captureInput init falied!")
return }
if captureSession.canAddInput(captureInput)
{
captureSession.addInput(captureInput)
}
let captureOutput = AVCaptureMetadataOutput()
if captureSession.canAddOutput(captureOutput)
{
captureSession.addOutput(captureOutput)
}
let queue = DispatchQueue.init(label: "captureOutputQueue")
captureOutput.setMetadataObjectsDelegate(self, queue: queue)
captureOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
captureOutput.rectOfInterest = self.rectOfInterest()
videoPreViewLayer = AVCaptureVideoPreviewLayer.init(session: self.captureSession)
videoPreViewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
}
//我这里是这么计算rectOfInterest的
rectOfInterest = CGRect(
x: _cropRect!.origin.y / ScreenSize.height ,
y: _cropRect!.origin.x / ScreenSize.width,
width: _cropRect!.size.height / ScreenSize.height ,
height: _cropRect!.size.width / ScreenSize.width
)
3 到这里,设备就已经准备好了。但是设备还没启动呢!
/** 开始捕捉*/
func startReader()
{
self.isReading = true;
if !captureSession.isRunning
{
self.captureSession.startRunning()
}
}
/* 停止捕捉*/
func stopReader()
{
self.isReading = false;
if captureSession.isRunning
{
captureSession.stopRunning()
}
}
/* 清楚资源*/
func clearCaputure()
{
self.isReading = false
self.stopReader()
if captureSession.inputs.count > 0
{
captureSession.removeInput(captureSession.inputs[0] as! AVCaptureInput)
}
if captureSession.outputs.count > 0
{
captureSession.removeOutput(captureSession.outputs[0] as! AVCaptureOutput)
}
}
4 在Manager类的 deinit方法里添加self.clearCaputure
deinit
{
self.clearCaputure()
}
5 最重要的回调来了。检查自己的delegate是否是QRCode的对象类型,以2s一次将结果回调到主线程。
// MARK : AVCaptureMetadataOutputObjectsDelegate
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)
{
if !self.isReading { return};
if metadataObjects != nil && metadataObjects.count > 0
{
let metadataObject:AVMetadataMachineReadableCodeObject = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
let codeType:NSString = metadataObject.type as NSString
let stringValue:NSString = metadataObject.stringValue as NSString
let currentDate = Date()
if codeType.hasSuffix("QRCode") || codeType.hasSuffix("Code128")
{
if nextTipDate != nil //2s回调一次,避免频繁的回调
{
if currentDate.compare(nextTipDate!) == .orderedAscending
{
return
}
else
{
self.nextTipDate = currentDate.addingTimeInterval(2.0)
}
}
else
{
nextTipDate = currentDate.addingTimeInterval(2.0)
}
DispatchQueue.main.async {
if self.delegate != nil && (self.delegate?.responds(to: #selector(QISCaptureManagerDelegate.didOutputDecodeStringValue(stringValue:))))!
{
self.delegate!.didOutputDecodeStringValue!(stringValue: stringValue)
}
}
return
}
DispatchQueue.main.async {
if self.delegate != nil && (self.delegate?.responds(to: #selector(QISCaptureManagerDelegate.didDecodeUnmatchType(codeType:))))!
{
self.delegate!.didDecodeUnmatchType!(codeType: stringValue)
}
}
}
}
6 在ViewController中关于此Manager的使用 示例,需要注意的是使用授权
var captureManager:MMBCaptureManager = MMBCaptureManager.init(croRect: MMBQRcodeOverlayView.cropRect())
captureManager.delegate = self
captureManager.rootViewController = self
self.addCapturePreviewSubLayer()
func addCapturePreviewSubLayer()
{
if captureManager.videoPreViewLayer != nil
{
captureManager.videoPreViewLayer!.frame = self.view.layer.bounds
self.view.layer.addSublayer(captureManager.videoPreViewLayer!)
self.view.bringSubview(toFront: overlayView!)
captureManager.startReader()
}
}
// MARK: QISCaptureManagerDelegate
func didChangeAccessCameraState(isGranted: Bool) {
if isGranted {
self.addCapturePreviewSubLayer()
}
else{
self.alertCameraAuth()
}
}
func didOutputDecodeStringValue(stringValue: NSString) {
debugPrint("scan result -- \(stringValue)")
}
好了,我会把自己相关ViewConroller和ScanView和此类上传到github,有需要的同学拿去用吧。祝安!!
`https://github.com/WeipengChan/ScanCodeDemo 完整demo github地址