172 lines
5.5 KiB
Swift
172 lines
5.5 KiB
Swift
//
|
|
// ScanVC.swift
|
|
// QuickLocation
|
|
//
|
|
// Created by 八条 on 2026/6/2.
|
|
//
|
|
|
|
import UIKit
|
|
import AVFoundation
|
|
import RxSwift
|
|
|
|
class ScanVC: BaseViewController {
|
|
|
|
override var isNavigationBarHidden: Bool { true }
|
|
|
|
fileprivate var rootView: ScanView!
|
|
|
|
override func loadView() {
|
|
rootView = ScanView(frame: UIScreen.main.bounds)
|
|
view = rootView
|
|
}
|
|
|
|
private var captureSession: AVCaptureSession!
|
|
private var previewLayer: AVCaptureVideoPreviewLayer!
|
|
private var isScanning = false
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupCamera()
|
|
}
|
|
|
|
override func viewDidLayoutSubviews() {
|
|
super.viewDidLayoutSubviews()
|
|
previewLayer?.frame = view.bounds
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
if let session = captureSession, !session.isRunning {
|
|
DispatchQueue.global(qos: .background).async {
|
|
session.startRunning()
|
|
}
|
|
}
|
|
startScanAnimation()
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
if let session = captureSession, session.isRunning {
|
|
session.stopRunning()
|
|
}
|
|
rootView.scanLineView.layer.removeAllAnimations()
|
|
}
|
|
|
|
private func handleScanResult(text: String) {
|
|
guard !isScanning else { return }
|
|
isScanning = true
|
|
captureSession.stopRunning()
|
|
rootView.scanLineView.alpha = 0
|
|
rootView.scanLineView.layer.removeAllAnimations()
|
|
requestJoinGroup(code: text)
|
|
}
|
|
|
|
// MARK: - API
|
|
private func requestJoinGroup(code: String) {
|
|
DLToast.showLoading()
|
|
GroupService.operate(opType: "join", requestData: ["share_code" : code]).subscribe(onNext: { response in
|
|
DLToast.show(text: "加入成功") {
|
|
NotificationCenter.default.post(name: .RefreshGroupInfoNotification, object: nil)
|
|
AppRouter.shared.popOrDismiss()
|
|
}
|
|
}, onError: { [weak self] (error) in
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
self?.isScanning = false
|
|
self?.startSession()
|
|
}
|
|
}).disposed(by: disposeBag)
|
|
}
|
|
|
|
private func startSession() {
|
|
if let session = captureSession, !session.isRunning {
|
|
DispatchQueue.global(qos: .background).async {
|
|
session.startRunning()
|
|
}
|
|
}
|
|
startScanAnimation()
|
|
}
|
|
|
|
// MARK: - 扫描线动画
|
|
private func startScanAnimation() {
|
|
rootView.scanLineView.layer.removeAllAnimations()
|
|
rootView.scanLineView.alpha = 1
|
|
rootView.scanLineView.frame.origin.y = 0
|
|
|
|
let boxH = rootView.scanBoxView.bounds.height
|
|
guard boxH > 0 else {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
self?.startScanAnimation()
|
|
}
|
|
return
|
|
}
|
|
|
|
UIView.animate(withDuration: 2.0, delay: 0, options: [.repeat, .curveLinear]) {
|
|
self.rootView.scanLineView.frame.origin.y = boxH - 2
|
|
}
|
|
}
|
|
|
|
// MARK: - 相机配置
|
|
private func setupCamera() {
|
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
switch status {
|
|
case .authorized:
|
|
break
|
|
case .notDetermined:
|
|
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
|
|
DispatchQueue.main.async {
|
|
if granted { self?.setupCamera() }
|
|
}
|
|
}
|
|
return
|
|
default:
|
|
DLToast.show(text: "请在设置中开启相机权限")
|
|
return
|
|
}
|
|
|
|
captureSession = AVCaptureSession()
|
|
captureSession.sessionPreset = .high
|
|
|
|
guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
|
|
DLToast.show(text: "未检测到摄像头")
|
|
return
|
|
}
|
|
|
|
do {
|
|
let input = try AVCaptureDeviceInput(device: videoDevice)
|
|
if captureSession.canAddInput(input) {
|
|
captureSession.addInput(input)
|
|
}
|
|
|
|
let output = AVCaptureMetadataOutput()
|
|
if captureSession.canAddOutput(output) {
|
|
captureSession.addOutput(output)
|
|
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
output.metadataObjectTypes = [.qr, .code128, .ean13, .ean8, .upce, .code39, .code39Mod43]
|
|
}
|
|
|
|
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
|
previewLayer.frame = view.bounds
|
|
previewLayer.videoGravity = .resizeAspectFill
|
|
view.layer.insertSublayer(previewLayer, at: 0)
|
|
|
|
DispatchQueue.global(qos: .background).async {
|
|
self.captureSession.startRunning()
|
|
}
|
|
|
|
} catch {
|
|
DLToast.show(text: "相机初始化失败:\(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - AVCaptureMetadataOutputObjectsDelegate
|
|
extension ScanVC: AVCaptureMetadataOutputObjectsDelegate {
|
|
func metadataOutput(_ output: AVCaptureMetadataOutput,
|
|
didOutput metadataObjects: [AVMetadataObject],
|
|
from connection: AVCaptureConnection) {
|
|
guard let obj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
|
|
let result = obj.stringValue else { return }
|
|
handleScanResult(text: result)
|
|
}
|
|
}
|