107 lines
3.3 KiB
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)
|
|
}
|
|
}
|