網上二維碼掃描的輪子實在是太多了,爲啥還要本身寫呢?實在是由於沒有找到合適的,找了十幾二十個輪子, swift 、oc的都找了,全都不支持橫豎屏切換,因此只能本身造了。這是一款使用Swift4編寫的二維碼掃描器,支持二維碼/條形碼的掃描,支持橫豎屏切換,支持連續掃描,可識別框內,使用超簡單,可擴展性強,很適合須要高度自定義小夥小姑涼們。git
由於不太喜歡storyBoard、xib那一套,因此界面是純手寫的,因此很方便移植哈~github
代碼地址:https://github.com/sunflowerseat/SwiftQRCodeswift
先貼一下使用方法,實在太簡單,就一個ViewController,根本懶得弄什麼pod,直接把SwiftQRCodeVC拷貝過來用就ok了, 記得要給權限 Privacy - Camera Usage Description
session
備註:聲音文件須要右鍵點擊項目名稱,add Files to "項目名稱",acc文件是無效的ide
掃描完成後,在func qrCodeCallBack(_ codeString : String?)
方法中寫回調 ,默認是連續掃描的佈局
裏面有些屬性方便自定義動畫
屬性名稱 | 屬性含義 |
---|---|
scanAnimationDuration | 掃描時長 |
needSound | 掃描結束是否須要播放聲音 |
scanWidth | 掃描框寬度 |
scanHeight | 掃描框高度 |
isRecoScanSize | 是否僅識別框內 |
scanBoxImagePath | 掃描框圖片 |
scanLineImagePath | 掃描線圖片 |
soundFilePath | 聲音文件 |
// // SwiftQRCodeVC.swift // // Created by fancy on 18/9/1. // Copyright © 2018年 fancy. All rights reserved. // import UIKit import AVFoundation 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 = "QRCode_ScanBox" //掃描框圖片 private let scanLineImagePath = "QRCode_ScanLine" //掃描線圖片 private let soundFilePath = "noticeMusic.caf" //聲音文件 class SwiftQRCodeVC: UIViewController{ var scanPane: UIImageView!///掃描框 var scanPreviewLayer : AVCaptureVideoPreviewLayer! //預覽圖層 var output : AVCaptureMetadataOutput! var scanSession : AVCaptureSession? lazy var scanLine : UIImageView = { let scanLine = UIImageView() scanLine.frame = CGRect(x: 0, y: 0, width: scanWidth, height: 3) scanLine.image = UIImage(named: scanLineImagePath) return scanLine }() override func viewDidLoad(){ super.viewDidLoad() //初始化界面 self.initView() //初始化ScanSession setupScanSession() } override func viewWillAppear(_ animated: Bool){ super.viewWillAppear(animated) startScan() } //初始化界面 func initView() { 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) } func addConstraint() { scanPane.translatesAutoresizingMaskIntoConstraints = false //建立約束 let widthConstraint = NSLayoutConstraint(item: scanPane, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: scanWidth) let heightConstraint = NSLayoutConstraint(item: scanPane, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: scanHeight) let centerX = NSLayoutConstraint(item: scanPane, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0) let centerY = NSLayoutConstraint(item: scanPane, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0) //添加多個約束 view.addConstraints([widthConstraint,heightConstraint,centerX,centerY]) } //初始化scanSession func setupScanSession(){ do{ //設置捕捉設備 let device = AVCaptureDevice.default(for: AVMediaType.video)! //設置設備輸入輸出 let input = try AVCaptureDeviceInput(device: device) let output = AVCaptureMetadataOutput() output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) self.output = output //設置會話 let scanSession = AVCaptureSession() scanSession.canSetSessionPreset(.high) if scanSession.canAddInput(input){ scanSession.addInput(input) } if scanSession.canAddOutput(output){ scanSession.addOutput(output) } //設置掃描類型(二維碼和條形碼) output.metadataObjectTypes = [ .qr, .code39, .code128, .code39Mod43, .ean13, .ean8, .code93 ] //預覽圖層 let scanPreviewLayer = 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) let screenOrientation = UIDevice.current.orientation if(screenOrientation == .portrait){ scanPreviewLayer.connection?.videoOrientation = .portrait }else if(screenOrientation == .landscapeLeft){ scanPreviewLayer.connection?.videoOrientation = .landscapeRight }else if(screenOrientation == .landscapeRight){ scanPreviewLayer.connection?.videoOrientation = .landscapeLeft }else if(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") guard let scanSession = scanSession else { return } if !scanSession.isRunning { scanSession.startRunning() } } //掃描動畫 private func scanAnimation() -> CABasicAnimation{ let startPoint = CGPoint(x: scanLine .center.x , y: 1) let endPoint = CGPoint(x: scanLine.center.x, y: scanHeight - 2) let translation = CABasicAnimation(keyPath: "position") translation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) translation.fromValue = NSValue(cgPoint: startPoint) translation.toValue = NSValue(cgPoint: endPoint) translation.duration = scanAnimationDuration translation.repeatCount = MAXFLOAT translation.autoreverses = true return translation } //MARK: - //MARK: Dealloc deinit{ ///移除通知 NotificationCenter.default.removeObserver(self) } } //MARK: - //MARK: AVCaptureMetadataOutputObjects Delegate extension SwiftQRCodeVC : AVCaptureMetadataOutputObjectsDelegate { //掃描捕捉完成 func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { //中止掃描 self.scanLine.layer.removeAllAnimations() self.scanSession!.stopRunning() //播放聲音 if(needSound){ self.playAlertSound() } //掃描完成 if metadataObjects.count > 0 { if let resultObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject{ self.confirm(title: "掃描結果", message: resultObj.stringValue, controller: self,handler: { (_) in //繼續掃描 self.startScan() }) } } } //彈出確認框 func confirm(title:String?,message:String?,controller:UIViewController,handler: ( (UIAlertAction) -> Swift.Void)? = nil){ let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let entureAction = UIAlertAction(title: "肯定", style: .destructive, handler: handler) alertVC.addAction(entureAction) controller.present(alertVC, animated: true, completion: nil) } //播放聲音 func playAlertSound(){ guard let soundPath = Bundle.main.path(forResource: soundFilePath, ofType: nil) else { return } guard let soundUrl = NSURL(string: soundPath) else { return } var soundID:SystemSoundID = 0 AudioServicesCreateSystemSoundID(soundUrl, &soundID) AudioServicesPlaySystemSound(soundID) } }
界面是有點粗製濫造哈~ 不過不要緊啊,在initView裏面稍微改改就能夠變成你想要的了。
代碼可能有更新,以github上的爲準,
對代碼有什麼疑問,能夠隨時來問 ,秋秋郵箱:970201861@qq.comspa