IOS 二维码扫描 横竖屏切换

news/2023/12/1 7:12:38

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上的为准,
对代码有什么疑问,可以随时来问 ,秋秋邮箱:970201861@qq.com


http://www.niftyadmin.cn/n/1934035.html

相关文章

为什么要用setTimeout模拟setInterval?

setTimeout和setInterval基本用法: 指定延迟时间后调用函数以指定周期调用函数使用setInterVal: function doStuff(){ // 此处为需要执行一段时间T的代码 } setInterVal(doStuff, 100); 复制代码下来看如何使用setTimeout模拟setInterval: fu…

关于c语言中指针的一些理解

在学习c语言的过程中,指针可谓是难点且重点,我们去看一些比较大型的程序常常会遇到关于指针的操作,最初对指针理解不够时,遇到这种程序立马便没了兴趣,在这里,深入自己对指针的了解主要是为了方便理解别人的…

桌面运维人员的健康漫谈

随着桌面式运维的加强,在电脑旁办公的时间越来越长,难免会出现颈椎、腰椎的问题,在日常的学习中总结了一点技巧,希望能保持健康。 变换姿势关键是要,如果你保持一个姿势数个小时不活动,你的脖子在数天后疼痛…

生成Excel.xlsx文件 iOS

使用到的三方库 https://github.com/jmcnamara/libxlsxwriter cocoapods导入 pod libxlsxwriter, ~> 0.8.3 1.引入头文件 #import <xlsxwriter/xlsxwriter.h> 2.示例代码 NSArray *array[{"name":"数学","time":"2018-10-21&quo…

TeaWeb v0.1 发布,经过千万级生产环境测试的第一个 Release 版本

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; TeaWeb是一个可视化的Web代理服务&#xff0c;目标是将代理服务、日志、统计、监控等集成在同一个服务中&#xff0c;让开发者简单地启动就可以使用。 v0.1版本是第一个Release版本&#xff0c;…

Json返回结果为null属性不显示解决方法

返回时null属性不显示&#xff1a;String str JSONObject.toJSONString(obj); 返回为null属性显示&#xff1a;String str JSONObject.toJSONString(obj,SerializerFeature.WriteMapNullValue); Fastjson的SerializerFeature序列化属性 QuoteFieldNames———-输出key时是否使…

基于Hadoop分布式集群搭建政企大数据计算存储服务平台_数道云科技

今天&#xff0c;小编就据目前互联网行业的发展&#xff0c;以及大数据Hadoop分布式集群等等来讲解一下&#xff0c;政企如何搭建大数据计算服务平台。互联网信息技术的迅猛发展&#xff0c;云计算、物联网、智能科技、AI、超级计算机等等的出现和发展&#xff0c;使数据量不断…

ubuntu安装mongodb

https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/

mongodb建索引

db.zk.ensureIndex({"u":1},{"unique":true})删除重复 from pymongo import MongoClient client MongoClient() db client.vip documentname db.zkkeys {} for k in documentname.find():key k[u]if keys.has_key(key):print duplicate key %s % keyd…

Markdown 编辑器 1.1.7 发布,新增全屏模式

百度智能云 云生态狂欢季 热门云产品1折起>>> react-markdown-editor 是一个基于 React 的 Markdown编辑器 组件。 新版更新内容&#xff1a; 添加全屏模式. 1ee6026修改markdown预览样式. ba3fc54修改编译脚本命令 b62c70e修复工具栏按钮类型导致Form表单提交问题.…

MySql语句分类

SQL语言分为四大类&#xff1a;数据定义语言DDL&#xff0c;数据查询语言DQL&#xff0c;数据操作语言DML&#xff0c;数据控制语言DCL 【1】数据定义语言DDL 数据定义语言DDL&#xff0c;用于创建、修改、删除数据库内的数据结构。如下&#xff1a; &#xff08;1&#xff09;…

[Note]Dockerfile中COPY命令的简单性

dockerfile中的COPY命令是不会拷贝目录结构的&#xff0c;它只会单纯把包含的所有文件拷贝到另一个目录中去。 相关链接&#xff1a;https://www.cnblogs.com/sparkdev/p/9573248.html 转载于:https://www.cnblogs.com/immortalBlog/p/10711897.html

java中类的加载顺序介绍(ClassLoader)

2019独角兽企业重金招聘Python工程师标准>>> java中类的加载顺序介绍(ClassLoader) 1、ClassNotFoundExcetpion   我们在开发中&#xff0c;经常可以遇见java.lang.ClassNotFoundExcetpion这个异常&#xff0c;今天我就来总结一下这个问题。对于这个异常&#x…

STM8S003F3通过PWM波实现三基色呼吸灯(转)

源&#xff1a; STM8S003F3通过PWM波实现三基色呼吸灯转载于:https://www.cnblogs.com/LittleTiger/p/10105311.html

mysql批量插入

insert into sms_membership (profile_id, group_id) values (164535 , 2078), (164280 , 2078);
最新文章