PLATFORM AND VERSION :iOS 18.5
I wanted to bring to your attention a critical issue some of our production users are experiencing with the CoinOut app. Specifically, users are encountering a problem when attempting to capture photos of receipts using the app's customized camera feature. The camera, which utilizes AVCaptureVideoPreviewLayer and AVCaptureDevice, occasionally fails to load the preview, resulting in a black screen instead of the expected camera view.
This camera blackout issue is significantly impacting the user experience as it prevents them from snapping photos of their receipts, which is a core functionality of the CoinOut app.
Any help/suggestion to this issue would be greatly appreciated.
STEPS TO REPRODUCE
Open the app and click on camera icon.
It will display camera to capture photo.
Camera shows black for few production user's.
class ViewController: UIViewController {
@IBOutlet private weak var captureButton: UIButton!
private var fillLayer: CAShapeLayer!
private var previewLayer : AVCaptureVideoPreviewLayer!
private var output: AVCapturePhotoOutput!
private var device: AVCaptureDevice!
private var session : AVCaptureSession!
private var highResolutionEnabled: Bool = false
private let sessionQueue = DispatchQueue(label: "session queue")
override func viewDidLoad() {
super.viewDidLoad()
setupCamera()
customiseUI()
}
@IBAction func startCamera(sender: UIButton) {
didTapTakePhoto()
}
private func setupCamera() {
let session = AVCaptureSession()
session.sessionPreset = AVCaptureSession.Preset.high
previewLayer = AVCaptureVideoPreviewLayer(session: session)
output = AVCapturePhotoOutput()
device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back)
if let device = self.device{
do{
let input = try AVCaptureDeviceInput(device: device)
if session.canAddInput(input){ session.addInput(input)}
else { print("\(#fileID):\(#function):\(#line) : Session Input addition failed") }
if session.canAddOutput(output){
output.isHighResolutionCaptureEnabled = self.highResolutionEnabled
session.addOutput(output)
} else { print("\(#fileID):\(#function):\(#line) : Session Input high resolution failed") }
previewLayer.videoGravity = .resizeAspectFill
previewLayer.session = session
sessionQueue.async { session.startRunning() }
self.session = session
self.session.accessibilityElementIsFocused()
try device.lockForConfiguration()
if device.isWhiteBalanceModeSupported(AVCaptureDevice.WhiteBalanceMode.autoWhiteBalance) {
device.whiteBalanceMode = .autoWhiteBalance
} else { print("\(#fileID):\(#function):\(#line) : isWhiteBalanceModeSupported no supported") }
if device.isWhiteBalanceModeSupported(AVCaptureDevice.WhiteBalanceMode.continuousAutoWhiteBalance) {
device.whiteBalanceMode = .continuousAutoWhiteBalance
} else { print("\(#fileID):\(#function):\(#line) : isWhiteBalanceModeSupported no supported") }
if device.isFocusModeSupported(.continuousAutoFocus) { device.focusMode = .continuousAutoFocus}
else if device.isFocusModeSupported(.autoFocus) { device.focusMode = .autoFocus }
device.unlockForConfiguration()
} catch { print("\(#fileID):\(#function):\(#line) : \(error.localizedDescription)") }
} else { print("\(#fileID):\(#function):\(#line) : Device found as nil") }
}
private func customiseUI() {
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height), cornerRadius: 0)
let rectangleWidth = view.frame.width - (view.frame.width * 0.16)
let x = (view.frame.width - rectangleWidth) / 2
let rectangleHeight = view.frame.height - (view.frame.height * 0.16)
let y = (view.frame.height - rectangleHeight) / 2
let roundRect = UIBezierPath(roundedRect: CGRect(x: x, y: y, width: rectangleWidth, height: rectangleHeight), byRoundingCorners:.allCorners, cornerRadii: CGSize(width: 0, height: 0))
roundRect.move(to: CGPoint(x: self.view.center.x , y: self.view.center.y))
path.append(roundRect)
path.usesEvenOddFillRule = true
fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.opacity = 0.4
previewLayer.addSublayer(fillLayer)
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
view.bringSubviewToFront(captureButton)
}
private func didTapTakePhoto() {
let settings = self.getSettings(camera: self.device)
if device.isAdjustingFocus {
do {
try device.lockForConfiguration()
device.focusMode = .continuousAutoFocus
device.unlockForConfiguration()
device.addObserver(self, forKeyPath: "adjustingFocus", options: [.new], context: nil)
} catch { print(error) }
} else { output.capturePhoto(with: settings, delegate: self) }
}
func getSettings(camera: AVCaptureDevice) -> AVCapturePhotoSettings {
var settings = AVCapturePhotoSettings()
if let rawFormat = output.availableRawPhotoPixelFormatTypes.first {
settings = AVCapturePhotoSettings(rawPixelFormatType: OSType(rawFormat))
}
settings.isHighResolutionPhotoEnabled = self.highResolutionEnabled
let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first!
let previewFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelType] as [String : Any]
settings.previewPhotoFormat = previewFormat
return settings
}
}
extension ViewController: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
AudioServicesDisposeSystemSoundID(1108)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard let data = photo.fileDataRepresentation() else { return }
let image = UIImage(data: data)!
showImage(cropped: image)
}
func showImage(cropped: UIImage) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ImagePreviewViewController") as? ImagePreviewViewController
vc?.captured = cropped
self.present(vc!, animated: true)
}
}```
Selecting any option will automatically load the page