Files
Ascently/ios/Ascently/Components/CameraImagePicker.swift

107 lines
3.3 KiB
Swift

import SwiftUI
import UIKit
/// A native iOS camera picker presented from a hosting controller so it can
/// respect all supported interface orientations. Present with `.fullScreenCover()`.
struct CameraImagePicker: UIViewControllerRepresentable {
@Environment(\.dismiss) private var dismiss
let onImageCaptured: (UIImage) -> Void
func makeUIViewController(context: Context) -> CameraHostViewController {
let host = CameraHostViewController()
host.onImageCaptured = { image in
onImageCaptured(image)
dismiss()
}
host.onCancel = {
dismiss()
}
host.pickerDelegate = context.coordinator
context.coordinator.onImageCaptured = { image in
onImageCaptured(image)
dismiss()
}
context.coordinator.onCancel = {
dismiss()
}
return host
}
func updateUIViewController(_ uiViewController: CameraHostViewController, context: Context) {
// No dynamic updates needed
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var onImageCaptured: ((UIImage) -> Void)?
var onCancel: (() -> Void)?
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
picker.dismiss(animated: true) {
if let image = info[.originalImage] as? UIImage {
self.onImageCaptured?(image)
} else {
self.onCancel?()
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true) {
self.onCancel?()
}
}
}
}
// MARK: - Hosting VC to own presentation/orientation
final class CameraHostViewController: UIViewController {
var onImageCaptured: ((UIImage) -> Void)?
var onCancel: (() -> Void)?
weak var pickerDelegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?
private var didPresent = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentIfNeeded()
}
private func presentIfNeeded() {
guard !didPresent, UIImagePickerController.isSourceTypeAvailable(.camera) else { return }
didPresent = true
let picker = UIImagePickerController()
picker.delegate = pickerDelegate
picker.sourceType = .camera
picker.cameraCaptureMode = .photo
picker.cameraDevice = .rear
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
present(picker, animated: true)
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
// Defer to app-supported orientations; returning .all allows rotation when permitted by the app
return .all
}
override var shouldAutorotate: Bool {
true
}
}
// MARK: - Camera Availability Check
extension CameraImagePicker {
static var isCameraAvailable: Bool {
UIImagePickerController.isSourceTypeAvailable(.camera)
}
}