实现相机扫描二维码, 相册选取图片识别二维码
import AVFoundation//二维码扫描
import CoreImage//二维码识别
import AudioToolbox//系统音效
AVCaptureMetadataOutputObjectsDelegate//扫描二维码
CALayerDelegate// CALayer 绘制
UINavigationControllerDelegate//图片选择控制器
UIImagePickerControllerDelegate
//调用摄像头
func setUpCamera() {
//创建 device
guard let cameraDevice = AVCaptureDevice.default(for: .video) else {
print("不支持摄像头")
return
}
//创建输入, 输出流
let deviceInput: AVCaptureInput
do{
deviceInput = try AVCaptureDeviceInput(device: cameraDevice)
}catch {
print("不支持摄像头")
return
}
let deviceOutput = AVCaptureMetadataOutput()
deviceOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//session 设置
let session = AVCaptureSession()
//采集质量
session.canSetSessionPreset(AVCaptureSession.Preset.high)
//添加输入, 输出流
if session.canAddInput(deviceInput) {
session.addInput(deviceInput)
}
if session.canAddOutput(deviceOutput) {
session.addOutput(deviceOutput)
}
//条码类型 AVMetadataObjectTypeQRCode, 设置支持的扫码所支持的格式
deviceOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.ean13,
AVMetadataObject.ObjectType.ean8,
AVMetadataObject.ObjectType.code128,
AVMetadataObject.ObjectType.qr]
//设置预览图层
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
previewLayer.frame = view.bounds
view.layer.insertSublayer(previewLayer, at: 0)
//设置扫描区域, 也可以自己写方法计算
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVCaptureInputPortFormatDescriptionDidChange, object: nil, queue: nil) { [weak self](noti) in
guard let strongSelf = self else { return }
deviceOutput.rectOfInterest = previewLayer.metadataOutputRectConverted(fromLayerRect: strongSelf.scanFrameView.frame)
}
//蒙板
let shadowLayer = CALayer()
shadowLayer.frame = view.bounds
shadowLayer.delegate = self
view.layer.insertSublayer(shadowLayer, above: previewLayer)
shadowLayer.setNeedsDisplay()
self.maskLayer = shadowLayer
self.session = session
}
//MARK: CALayerDelegate, 创建蒙板
func draw(_ layer: CALayer, in ctx: CGContext) {
if layer == maskLayer {
UIGraphicsBeginImageContextWithOptions((maskLayer?.frame.size)!, false, 1)
//蒙板颜色
ctx.setFillColor(UIColor.RGBColor(r: 47, g: 47, b: 47, alpha: 0.6).cgColor)
ctx.fill((maskLayer?.frame)!)
let scanFrame = view.convert(scanFrameView.frame, from: scanFrameView.superview)
//空出中间一块
ctx.clear(scanFrame)
}
}
//扫描线移动
func scanAction() {
let startPoint = CGPoint(x: scanline.center.x, y: scanFrameView.frame.minY)
let endPoint = CGPoint(x: scanline.center.x, y: scanFrameView.frame.maxY)
let basicAnimation = CABasicAnimation(keyPath: "position")
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.fromValue = NSValue(cgPoint: startPoint)
basicAnimation.toValue = NSValue(cgPoint: endPoint)
basicAnimation.duration = 4
basicAnimation.repeatCount = MAXFLOAT
basicAnimation.autoreverses = false
scanline.layer.add(basicAnimation, forKey: nil)
}
//自定义方法计算扫描框尺寸
func rectOfInterestByScanViewRect(rect:CGRect) -> CGRect{
let width = self.view.frame.size.width
let height = self.view.frame.size.height
let x = rect.minY / height
let y = rect.minX / width
let w = rect.size.height / height
let h = rect.size.width / width
return CGRect(x: x, y: y, width: w, height: h)
}
//系统方法实现
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVCaptureInputPortFormatDescriptionDidChange, object: nil, queue: nil) { [weak self](noti) in
guard let strongSelf = self else { return }
deviceOutput.rectOfInterest = previewLayer.metadataOutputRectConverted(fromLayerRect: strongSelf.scanFrameView.frame)
}
deinit {
maskLayer?.delegate = nil
NotificationCenter.default.removeObserver(self)
print("deinit ~ \(self)")
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate
//捕获条码代理协议
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if metadataObjects.count > 0 {
playSystemSound()
//停止扫描
session?.stopRunning()
//取识别到的第一个二维码
let metadataobject = metadataObjects.first as? AVMetadataMachineReadableCodeObject
//二维码的值
stringValue = metadataobject?.stringValue
//todoSomething.....
}
}
//MARK: UIImagePickerControllerDelegate
//打开本地相册
func openLocalPhoto() {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .photoLibrary
imagePicker.delegate = self
self.present(imagePicker, animated: true, completion: nil)
}
//选择完成
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//选择的类型是照片
let type = info[UIImagePickerController.InfoKey.mediaType] as! String
if type == "public.image" {
//获取照片
pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
print(pickedImage.size)
picker.dismiss(animated: true) {
self.scanQRCodeFromPhotoLibrary(image: self.pickedImage)
}
}
}
//取消选择
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
//识别二维码
@objc func scanQRCodeFromPhotoLibrary(image: UIImage) {
guard let cgImage = image.cgImage else { return }
/// 这里设置了识别的精准程度为High,不过这个可能会有一点耗时。
if let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) {
let features = detector.features(in: CIImage(cgImage: cgImage))
for feature in features { // 这里实际上可以识别两张二维码,在这里只取第一张(左边或者上边)
if let qrFeature = feature as? CIQRCodeFeature {
playSystemSound()
session?.stopRunning()
//获取识别出的字符串
stringValue = qrFeature.messageString
//to do something.....
return
}
}
}else {
//没有识别到二维码
//to do something...
}
}
func playSystemSound() {
DispatchQueue.global().async {
//播放音效
let url = Bundle.main.url(forResource: "scanSuccess.wav", withExtension: nil)
var soundID: SystemSoundID = 8787
AudioServicesCreateSystemSoundID(url! as CFURL, &soundID)
AudioServicesPlaySystemSound(soundID)
}
}