importUIKit
importAVFoundation
private let scanAnimationDuration = 3.0//扫描时长
private let needSound = true //扫描结束是否需要播放声音
private let scanWidth : CGFloat = 300 //扫描框宽度
private let scanHeight : CGFloat = 300 //扫描框高度
private let isRecoScanSize = true //是否仅识别框内
private let scanBoxImagePath = "扫描框" //扫描框图片
private let scanLineImagePath = "扫描线" //扫描线图片
private let soundFilePath = "noticeMusic.caf" //声音文件
class ZWScanQRCodeVCJoe: UIViewController{
letcancelBtn=UIButton()//取消返回按钮
var scanPane: UIImageView!///扫描框
var scanPreviewLayer : AVCaptureVideoPreviewLayer! //预览图层
var output : AVCaptureMetadataOutput!
var scanSession: AVCaptureSession?
lazyvarscanLine:UIImageView= {
letscanLine =UIImageView()
scanLine.frame=CGRect(x:0,y:0,width:scanWidth,height:3)
scanLine.image=UIImage(named:scanLineImagePath)
returnscanLine
}()
override func viewDidLoad(){
super.viewDidLoad()
//初始化界面
self.initView()
//初始化ScanSession
setupScanSession()
// 监听屏幕旋转
// NotificationCenter.default.addObserver(self, selector: #selector(receiverNotification), name: UIDevice.orientationDidChangeNotification, object: nil)
self.receiverNotification()
}
@objc func receiverNotification() {
setLayerOrientationByDeviceOritation()
}
overridefuncviewWillAppear(_animated:Bool){
super.viewWillAppear(animated)
startScan()
}
//初始化界面
funcinitView() {
//取消按钮 返回按钮
cancelBtn.frame = CGRect(x: 63*WidthW, y: 70*WidthW, width: 92*WidthW, height: 92*WidthW)
// cancelBtn.backgroundColor = UIColor.gray
cancelBtn.setTitleColor(UIColor.white, for: .normal)
cancelBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18)
cancelBtn.layer.cornerRadius = 3
cancelBtn.clipsToBounds = true
cancelBtn.tag=1
cancelBtn.setImage(UIImage.init(named: "返回"), for: .normal)
cancelBtn.addTarget(self, action: #selector(closeBtnClick), for: .touchUpInside)
self.view.addSubview(cancelBtn)
//
scanPane=UIImageView()
scanPane.frame=CGRect(x:300,y:100,width:400,height:400)
scanPane.image = UIImage(named: scanBoxImagePath)
self.view.addSubview(scanPane)
//增加约束
addConstraint()
scanPane.addSubview(scanLine)
}
//返回页面
@objc func closeBtnClick(){
self.view.viewContainingController()?.navigationController?.popViewController(animated: false)
}
//扫描完成回调
funcqrCodeCallBack(_codeString :String?) {
self.confirm(title:"扫描结果",message: codeString,controller:self,handler: { (_)in
//继续扫描
self.startScan()
})
}
func addConstraint() {
scanPane.translatesAutoresizingMaskIntoConstraints = false
//创建约束
let widthConstraint = NSLayoutConstraint(item: scanPane as Any, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: scanWidth)
let heightConstraint = NSLayoutConstraint(item: scanPane as Any, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: scanHeight)
let centerX = NSLayoutConstraint(item: scanPane as Any, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0)
let centerY = NSLayoutConstraint(item: scanPane as Any, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0)
//添加多个约束
view.addConstraints([widthConstraint,heightConstraint,centerX,centerY])
}
//初始化scanSession
func setupScanSession(){
do{
//设置捕捉设备
letdevice =AVCaptureDevice.default(for:AVMediaType.video)!
//设置设备输入输出
letinput =tryAVCaptureDeviceInput(device: device)
letoutput =AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self,queue: DispatchQueue.main)
self.output= output
//设置会话
let scanSession =AVCaptureSession()
scanSession.canSetSessionPreset(.high)
ifscanSession.canAddInput(input){
scanSession.addInput(input)
}
ifscanSession.canAddOutput(output){
scanSession.addOutput(output)
}
//设置扫描类型(二维码和条形码)
output.metadataObjectTypes= [
.qr,
.code39,
.code128,
.code39Mod43,
.ean13,
.ean8,
.code93
]
//预览图层
letscanPreviewLayer =AVCaptureVideoPreviewLayer(session:scanSession)
scanPreviewLayer.videoGravity=AVLayerVideoGravity.resizeAspectFill
scanPreviewLayer.frame=view.layer.bounds
self.scanPreviewLayer= scanPreviewLayer
setLayerOrientationByDeviceOritation()
//保存会话
self.scanSession= scanSession
}catch{
//摄像头不可用
self.confirm(title: "温馨提示", message: "摄像头不可用", controller: self)
return
}
}
func setLayerOrientationByDeviceOritation() {
if(scanPreviewLayer == nil){
return
}
scanPreviewLayer.frame = view.layer.bounds
view.layer.insertSublayer(scanPreviewLayer, at: 0)
letscreenOrientation =UIDevice.current.orientation
if(screenOrientation == .portrait){
scanPreviewLayer.connection?.videoOrientation = .portrait
}elseif(screenOrientation == .landscapeLeft){
scanPreviewLayer.connection?.videoOrientation = .landscapeRight
}elseif(screenOrientation == .landscapeRight){
scanPreviewLayer.connection?.videoOrientation = .landscapeLeft
}elseif(screenOrientation == .portraitUpsideDown){
scanPreviewLayer.connection?.videoOrientation = .portraitUpsideDown
}else{
scanPreviewLayer.connection?.videoOrientation = .landscapeRight
}
//设置扫描区域
NotificationCenter.default.addObserver(forName:NSNotification.Name.AVCaptureInputPortFormatDescriptionDidChange,object:nil,queue:nil,using: { (noti)in
if(isRecoScanSize){
self.output.rectOfInterest = self.scanPreviewLayer.metadataOutputRectConverted(fromLayerRect: self.scanPane.frame)
}else{
self.output.rectOfInterest=CGRect(x:0,y:0,width:1,height:1)
}
})
}
//设备旋转后重新布局
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setLayerOrientationByDeviceOritation()
}
//开始扫描
fileprivate func startScan(){
scanLine.layer.add(scanAnimation(), forKey: "scan")
guardletscanSession =scanSessionelse{return}
if!scanSession.isRunning
{
scanSession.startRunning()
}
}
//扫描动画
private func scanAnimation() -> CABasicAnimation{
letstartPoint =CGPoint(x:scanLine.center.x ,y:1)
letendPoint =CGPoint(x:scanLine.center.x,y:scanHeight-2)
lettranslation =CABasicAnimation(keyPath:"position")
translation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
translation.fromValue=NSValue(cgPoint: startPoint)
translation.toValue=NSValue(cgPoint: endPoint)
translation.duration=scanAnimationDuration
translation.repeatCount=MAXFLOAT
translation.autoreverses=true
returntranslation
}
//MARK: -
//MARK: Dealloc
deinit{
///移除通知
NotificationCenter.default.removeObserver(self)
}
}
//MARK: -
//MARK: AVCaptureMetadataOutputObjects Delegate
extension ZWScanQRCodeVCJoe : AVCaptureMetadataOutputObjectsDelegate
{
//捕捉扫描结果
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
//停止扫描
self.scanLine.layer.removeAllAnimations()
self.scanSession!.stopRunning()
//播放声音
if(needSound){
self.playAlertSound()
}
//扫描完成
ifmetadataObjects.count>0{
ifletresultObj = metadataObjects.firstas?AVMetadataMachineReadableCodeObject{
self.qrCodeCallBack(resultObj.stringValue)
}
}
}
//弹出确认框
func confirm(title:String?,message:String?,controller:UIViewController,handler: ( (UIAlertAction) -> Swift.Void)? = nil){
letalertVC =UIAlertController(title: title,message: message,preferredStyle: .alert)
letentureAction =UIAlertAction(title:"确定",style: .destructive,handler: handler)
alertVC.addAction(entureAction)
controller.present(alertVC,animated:true,completion:nil)
}
//播放声音
func playAlertSound(){
guardletsoundPath =Bundle.main.path(forResource:soundFilePath,ofType:nil) else{return}
guardletsoundUrl =NSURL(string: soundPath)else{return}
varsoundID:SystemSoundID=0
AudioServicesCreateSystemSoundID(soundUrl, &soundID)
AudioServicesPlaySystemSound(soundID)
}
}