jsdw_ios/QuickLocation/Section/Scan/ScanVC.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)
}
}