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]