最近在做的一个项目里面包含了扫码逻辑,因为之前公司的一些项目用的是旧的第三方框架,准确性和速度都与原生存在一定的差距,界面可变性局限大,维护成本高,不能忍😤,所以自己用原生API重写了一个扫码模块,Github具体代码可点击此处。官方的扫码API是在iOS8上推出的,如今系统迭代到了iOS10,iOS8以下系统的市场占有率已经可以忽略,并且APP适配现在也是从iOS8开始。
实现方法
这里使用ViewController进行实现,之前考虑过用View,因为生命周期的那里坑比较多,为了方便管理生命周期所以采用了ViewController进行实现。
工程设置
使用JBScanViewController,项目工程info.plist需要添加相机、相册权限(适配iOS 10)和将系统StatusBar的优先级调低,否则在相册选择图片进行裁切时会有20PX的下沉。
原生API的调用
头文件需要用到AVFoundation框架和MobileCoreServices,相机的操作依赖AVFoundation,MobileCoreServices则是在约束相册筛选类型时使用。
ViewController需要遵循AVCaptureMetadataOutputObjectsDelegate
、UINavigationControllerDelegate
、UIImagePickerControllerDelegate
三个代理,AVCaptureMetadataOutputObjectsDelegate是接受相机输入流,其它两个则是调用系统相册。
首先需要声明中间SessionAVCaptureSession * session; //输入输出的中间桥梁
进行初始化摄像设备和扫码行为
1 | //获取摄像设备 |
扫码范围这里要计算好,特别需要注意的是此处坐标与正常CGRect不同,此处是(Y,X,HEIGHT,WIDTH)
并且全部按比例(0-1)设定,设定了扫码范围后系统只会判断该范围内的数据,这样能显著仅提高效率和成功率,JBScanViewController设定了一些宏参数以灵活改变范围大小和偏移量可根据需求进行修改
1 |
执行完startRunning之后就开始进行扫描了,但是这里会涉及到生命周期的一些问题,如果将这一段代码在ViewDidLoad
方法里面进行初始化则会有0.5-1秒的延时,因为加载相机会消耗大量的时间,所以界面会等到相机加载完之后才Push,为了不产生卡顿感,这里做法是将初始化方法放到ViewDidAppear
方法中进行,先将页面Push,再初始化相机,这样会非常流畅,但是先push过来回有黑屏的界面,因为相机还在启动过程中,所以加上一层UI引导用户进行等待。
1 | //启动提示和loading菊花 |
得到扫描结果之后会调用以下两个回调
1 |
|
这里得到的结果会返回到我封装的两个方法中,每得到一次结果会同时调用这两个方法,可在里面进行数据操作
1 |
|
UI界面与动画
扫描框蒙版UI和动画
因为考虑到可集成性,JBScanViewController使用纯代码进行UI构建,主要使用贝塞尔曲线在Layer层进行动画。首先需要在相机层上方进行一层蒙版处理
1 | //扫描框 |
将蒙版通过贝塞尔矩形路径镂空,通过控制蒙版Layer的Mask属性加上CABasicAnimation
的Path动画使蒙版出现生成动画
1 | /** 加载扫描框生成动画 */ |
扫描框四角UI和动画
这时蒙版生成的动画已经完成,我们需要的还有边框与扫描线的UI和动画,将扫描框拆分为四个角进行绘制,每个角的绘制方向按顺时针绘制,并且X=Y=边框边长乘以边长与X的比例,所以当我们更改比例至0.5时就能实现全边框包裹。
扫描框初始值(原点值)
1 | //扫描框 |
扫描框目标值
1 | /** |
扫描线UI和动画
最后加上扫描线的UI和动画就能完成了,扫描线需要用到一个矩形Layer层加上渐变色,然后将其Mask属性添加椭圆Path遮罩,最后生成扫描线边缘半透明的效果,因为渐变层Frame属性不能添加动画,所以将动画添加到Position属性,通过改变起始点和终止点来完成从无到有的动画。扫描动画同理改变其Position的高度值,但是要特别注意removedOnCompletion
属性,否则按home键退出时会停止扫描动画。
1 | /** |
其它方法
提供了相册读取方法和闪光灯开启/关闭方法,可自行调用
1 |
|
JBScanViewController大部分代码实现的功能是UI和动画,对整体的时长和美观度进行了考量,同时兼顾性能和界面流畅度,也抽离出常用属性以适应不同的项目需求。具体使用可下载Demo进行了解。