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

请求AVCaptureDevice相机权限导致TabView选中标签自动切换问题

TabView请求相机权限时自动切回首个标签的问题解决

问题描述

在以TabView为根视图的SwiftUI应用中添加二维码扫描功能时,弹出相机权限请求对话框后,当前选中的第二个标签会自动切回第一个标签。调整CameraView在TabView中的顺序(设为第一个标签)时问题消失,但需要保持相机标签为第二个标签。

复现代码

ContentView

import SwiftUI

enum Tab: Int {
    case first = 1, second
}

struct ContentView: View {
    @SceneStorage("selectedTab") var selectedTab: Int?
    init() {
        selectedTab = Tab.first.rawValue
    }
    var body: some View {
        TabView {
            TabView(selection: $selectedTab) {
                AnyView()
                    .tabItem {
                        Label("Calendar", systemImage: "calendar")
                    }
                    .tag(Tab.first)
                CameraView()
                    .tabItem {
                        Label("Camera", systemImage: "person.fill")
                    }
                    .tag(Tab.second)     
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CameraView

import SwiftUI
import UIKit
import AVFoundation

struct CameraView: UIViewRepresentable {
    typealias UIViewType = CameraPreview
    
    private let session = AVCaptureSession()
    private let metadataOutput = AVCaptureMetadataOutput()

    func setupCamera(_ uiView: CameraPreview) {
        if let backCamera = AVCaptureDevice.default(for: AVMediaType.video) {
            if let input = try? AVCaptureDeviceInput(device: backCamera) {
                session.sessionPreset = .photo
                
                if session.canAddInput(input) {
                    session.addInput(input)
                }
                if session.canAddOutput(metadataOutput) {
                    session.addOutput(metadataOutput)
                }
                let previewLayer = AVCaptureVideoPreviewLayer(session: session)
                
                uiView.backgroundColor = UIColor.gray
                previewLayer.videoGravity = .resizeAspectFill
                uiView.layer.addSublayer(previewLayer)
                uiView.previewLayer = previewLayer
                
                session.startRunning()
            }
        }
        
    }
    
    func makeUIView(context: UIViewRepresentableContext<CameraView>) -> CameraView.UIViewType {
        let cameraView = CameraPreview(session: session)
        
        checkCameraAuthorizationStatus(cameraView)
        
        return cameraView
    }
    
    static func dismantleUIView(_ uiView: CameraPreview, coordinator: ()) {
        uiView.session.stopRunning()
    }
    
    private func checkCameraAuthorizationStatus(_ uiView: CameraPreview) {
        let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
        if cameraAuthorizationStatus == .authorized {
            setupCamera(uiView)
        } else {
            AVCaptureDevice.requestAccess(for: .video) { granted in
                DispatchQueue.main.sync {
                    if granted {
                        self.setupCamera(uiView)
                    }
                }
            }
        }
    }
    
    func updateUIView(_ uiView: CameraPreview, context: UIViewRepresentableContext<CameraView>) {
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
    }
    
}

CameraPreview

import UIKit
import AVFoundation


class CameraPreview: UIView {
    
    private var label:UILabel?
    
    var previewLayer: AVCaptureVideoPreviewLayer?
    var session = AVCaptureSession()
    
    init(session: AVCaptureSession) {
        super.init(frame: .zero)
        self.session = session
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        previewLayer?.frame = self.bounds
    }
}

问题原因

  1. 无效的TabView嵌套:ContentView中嵌套了两层TabView,外层TabView没有配置任何tabItem,会干扰内层TabView的选中状态管理,权限弹窗触发视图更新时,选中状态被意外重置。
  2. 主线程阻塞风险:CameraView中使用DispatchQueue.main.sync处理权限回调,可能导致主线程阻塞,触发SwiftUI视图的意外重建,进而执行init中的代码重置选中标签。
  3. SceneStorage初始化逻辑错误:在ContentView的init中直接设置selectedTab,会在视图重建时覆盖用户当前选中的持久化状态。
  4. Tag类型不匹配:TabView的tag使用了Tab枚举实例,但selectedTab是Int?类型,类型不匹配可能导致选中状态识别异常。

修复方案及代码调整

1. 移除嵌套TabView,修正选中状态绑定

将外层无用的TabView移除,直接使用带selection绑定的TabView作为根视图,同时统一tag与selectedTab的类型:

import SwiftUI

enum Tab: Int {
    case first = 1, second
}

struct ContentView: View {
    // 将selectedTab改为非可选Int类型,直接声明默认值,避免init覆盖持久化状态
    @SceneStorage("selectedTab") var selectedTab: Int = Tab.first.rawValue
    
    var body: some View {
        TabView(selection: $selectedTab) {
            AnyView()
                .tabItem {
                    Label("Calendar", systemImage: "calendar")
                }
                .tag(Tab.first.rawValue) // 使用枚举rawValue,与selectedTab类型匹配
            CameraView()
                .tabItem {
                    Label("Camera", systemImage: "person.fill")
                }
                .tag(Tab.second.rawValue)     
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2. 修正权限回调的线程处理

将DispatchQueue.main.sync改为DispatchQueue.main.async,避免主线程阻塞:

private func checkCameraAuthorizationStatus(_ uiView: CameraPreview) {
    let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
    if cameraAuthorizationStatus == .authorized {
        setupCamera(uiView)
    } else {
        AVCaptureDevice.requestAccess(for: .video) { granted in
            // 使用async避免主线程阻塞
            DispatchQueue.main.async {
                if granted {
                    self.setupCamera(uiView)
                }
            }
        }
    }
}

修复说明

  • 移除嵌套TabView后,TabView的选中状态管理变得单一且正确,不会因多余的视图层级导致状态混乱。
  • 调整SceneStorage的初始化方式后,仅在首次启动时使用默认值,后续会保留用户的选中状态,不会在视图重建时重置。
  • 替换sync为async避免了主线程阻塞,防止因线程问题触发的视图意外重建。
  • 统一tag与selectedTab的类型,确保选中状态的识别准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 14:10:30