IOS 二維碼掃描 橫豎屏切換

Swift4 二維碼掃描 支持橫豎屏切換

網上二維碼掃描的輪子實在是太多了,爲啥還要自己寫呢?實在是因爲沒有找到合適的,找了十幾二十個輪子, swift 、oc的都找了,全都不支持橫豎屏切換,所以只能自己造了。

這是一款使用Swift4編寫的二維碼掃描器,支持二維碼/條形碼的掃描,支持橫豎屏切換,支持連續掃描,可識別框內,使用超簡單,可擴展性強,很適合需要高度自定義小夥小姑涼們。

因爲不太喜歡storyBoard、xib那一套,所以界面是純手寫的,所以很方便移植哈~

使用

代碼地址:https://github.com/sunflowerseat/SwiftQRCode

先貼一下使用方法,實在太簡單,就一個ViewController,根本懶得弄什麼pod,直接把SwiftQRCodeVC拷貝過來用就ok了, 記得要給權限 Privacy - Camera Usage Description

備註:聲音文件需要右鍵點擊項目名稱,add Files to "項目名稱",acc文件是無效的

掃描完成後,在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上的爲準,
對代碼有什麼疑問,可以隨時來問 ,秋秋郵箱:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章