iOS中AVCaptureMetadataOutput功能覆盖导航栏的技术问询
解决AVCaptureMetadataOutput预览层覆盖导航栏的问题
你遇到的这个问题我之前做扫码功能时也碰到过,核心原因基本都是预览层的布局没处理好——要么是直接把AVCaptureVideoPreviewLayer加到了控制器的view上,且没有避开导航栏/状态栏区域;要么是会话和预览层被当成局部变量提前释放导致异常。我给你整理了一套完整的修正方案,你可以参考调整:
关键修正点&完整代码
首先要把会话和预览层设为类的属性(避免方法执行完被ARC释放),然后通过安全区域布局来让预览层避开导航栏:
import UIKit import AVFoundation class ScanViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { // 把会话和预览层设为类属性,防止提前被释放 private var captureSession: AVCaptureSession! private var previewLayer: AVCaptureVideoPreviewLayer! override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() setupPreviewLayer() } // 初始化捕获会话 private func setupCaptureSession() { captureSession = AVCaptureSession() // 按需调整分辨率,.high是平衡清晰度和性能的选择 captureSession.sessionPreset = .high // 获取后置摄像头 guard let captureDevice = AVCaptureDevice.default(for: .video) else { print("无法获取后置摄像头,请检查权限") return } do { let input = try AVCaptureDeviceInput(device: captureDevice) if captureSession.canAddInput(input) { captureSession.addInput(input) } // 配置元数据输出(扫码核心) let metadataOutput = AVCaptureMetadataOutput() if captureSession.canAddOutput(metadataOutput) { captureSession.addOutput(metadataOutput) // 设置代理和识别的码类型,这里以QR码为例 metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) metadataOutput.metadataObjectTypes = [.qr] } } catch { print("摄像头初始化失败:\(error.localizedDescription)") return } } // 配置预览层并布局 private func setupPreviewLayer() { previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.videoGravity = .resizeAspectFill // 保持视频比例填充 // 重点:用安全区域布局避开导航栏/状态栏 // 方式1:直接设置frame为安全区域的范围(适合不需要适配旋转的场景) previewLayer.frame = view.safeAreaLayoutGuide.layoutFrame view.layer.addSublayer(previewLayer) // 方式2:用AutoLayout约束(推荐,适配屏幕旋转) // previewLayer.translatesAutoresizingMaskIntoConstraints = false // view.layer.addSublayer(previewLayer) // NSLayoutConstraint.activate([ // previewLayer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), // previewLayer.leadingAnchor.constraint(equalTo: view.leadingAnchor), // previewLayer.trailingAnchor.constraint(equalTo: view.trailingAnchor), // previewLayer.bottomAnchor.constraint(equalTo: view.bottomAnchor) // ]) // 启动扫码会话 captureSession.startRunning() } // 处理扫码结果的代理方法 func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadata = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let scanResult = metadata.stringValue else { return } // 这里处理你拿到的扫码结果 print("识别到内容:\(scanResult)") // 如果需要停止扫码,可以调用 captureSession.stopRunning() } }
额外注意事项
- 导航栏透明度影响:如果你的导航栏设置了
isTranslucent = true(默认是true),控制器的view会延伸到导航栏下方,这时候必须用安全区域来约束预览层;如果不需要透明导航栏,直接设置navigationController?.navigationBar.isTranslucent = false,这样控制器的view会从导航栏下方开始,直接设置预览层为view.bounds也不会覆盖导航栏。 - 权限检查:记得在Info.plist中添加
NSCameraUsageDescription权限描述,不然会崩溃。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

