You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
    }
}

额外注意事项

  1. 导航栏透明度影响:如果你的导航栏设置了isTranslucent = true(默认是true),控制器的view会延伸到导航栏下方,这时候必须用安全区域来约束预览层;如果不需要透明导航栏,直接设置navigationController?.navigationBar.isTranslucent = false,这样控制器的view会从导航栏下方开始,直接设置预览层为view.bounds也不会覆盖导航栏。
  2. 权限检查:记得在Info.plist中添加NSCameraUsageDescription权限描述,不然会崩溃。

内容的提问来源于stack exchange,提问作者Robert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:51:03