Fix black screen on QR scanner after first camera permission grant.

Update preview layer layout and session lifecycle when authorization completes asynchronously.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 09:15:06 +08:00
parent fe84a7e8f4
commit 731de7d7f0

View File

@ -14,6 +14,7 @@ final class QRCodeScannerViewController: BaseViewController, AVCaptureMetadataOu
private let captureSession = AVCaptureSession() private let captureSession = AVCaptureSession()
private var previewLayer: AVCaptureVideoPreviewLayer? private var previewLayer: AVCaptureVideoPreviewLayer?
private var isCameraConfigured = false
private var hasHandledResult = false private var hasHandledResult = false
private let maskView = UIView() private let maskView = UIView()
@ -60,17 +61,21 @@ final class QRCodeScannerViewController: BaseViewController, AVCaptureMetadataOu
checkCameraPermission() checkCameraPermission()
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updatePreviewLayout()
startCaptureSessionIfNeeded()
}
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
previewLayer?.frame = view.bounds updatePreviewLayout()
updateMask() updateMask()
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
if captureSession.isRunning { stopCaptureSession()
captureSession.stopRunning()
}
} }
private func checkCameraPermission() { private func checkCameraPermission() {
@ -94,11 +99,21 @@ final class QRCodeScannerViewController: BaseViewController, AVCaptureMetadataOu
} }
private func setupCamera() { private func setupCamera() {
if isCameraConfigured {
updatePreviewLayout()
startCaptureSessionIfNeeded()
return
}
guard let device = AVCaptureDevice.default(for: .video), guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { let input = try? AVCaptureDeviceInput(device: device) else {
showToast("无法打开相机") showToast("无法打开相机")
return return
} }
captureSession.beginConfiguration()
defer { captureSession.commitConfiguration() }
guard captureSession.canAddInput(input) else { return } guard captureSession.canAddInput(input) else { return }
captureSession.addInput(input) captureSession.addInput(input)
@ -112,12 +127,30 @@ final class QRCodeScannerViewController: BaseViewController, AVCaptureMetadataOu
layer.videoGravity = .resizeAspectFill layer.videoGravity = .resizeAspectFill
view.layer.insertSublayer(layer, at: 0) view.layer.insertSublayer(layer, at: 0)
previewLayer = layer previewLayer = layer
isCameraConfigured = true
updatePreviewLayout()
startCaptureSessionIfNeeded()
}
private func updatePreviewLayout() {
previewLayer?.frame = view.bounds
}
private func startCaptureSessionIfNeeded() {
guard isCameraConfigured, !captureSession.isRunning else { return }
DispatchQueue.global(qos: .userInitiated).async { [weak self] in DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession.startRunning() self?.captureSession.startRunning()
} }
} }
private func stopCaptureSession() {
guard captureSession.isRunning else { return }
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession.stopRunning()
}
}
private func updateMask() { private func updateMask() {
maskView.layer.sublayers?.forEach { $0.removeFromSuperlayer() } maskView.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
let border = CAShapeLayer() let border = CAShapeLayer()
@ -139,7 +172,7 @@ final class QRCodeScannerViewController: BaseViewController, AVCaptureMetadataOu
let value = object.stringValue, let value = object.stringValue,
!value.isEmpty else { return } !value.isEmpty else { return }
hasHandledResult = true hasHandledResult = true
captureSession.stopRunning() stopCaptureSession()
onScanResult?(value) onScanResult?(value)
dismiss(animated: true) dismiss(animated: true)
} }