// // Copyright (c) 2023 Loverde Co. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(UIKit) #if canImport(UIKit) import UIKit #endif import AVFoundation import Photos /// A protocol that defines the methods an image picker delegate should implement. public protocol ImagePickerControllerDelegate: AnyObject { /// Tells the delegate that an image has been selected by the image picker. /// - Parameter image: The `UIImage` that was selected, or `nil` if the selection was canceled or failed. func imagePicker(didSelect image: UIImage?) } /// A custom `UIViewController` that provides functionality for picking images from the camera or photo library. /// /// This controller handles permissions for camera and photo library access and presents /// a `UIImagePickerController` to the user. public class ImagePickerController: UIViewController, UINavigationControllerDelegate { private var isAlertOpen: Bool = false private var imagePickerController: UIImagePickerController = UIImagePickerController() /// The delegate for the `ImagePickerController`, which will receive callbacks when an image is selected. public weak var delegate: ImagePickerControllerDelegate? /// A boolean value that determines whether the user can edit the selected image. Defaults to `false`. public var isEditable: Bool = false /// Initializes a new `ImagePickerController` instance. public init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } public override func viewDidLoad() { super.viewDidLoad() self.imagePickerController.delegate = self self.imagePickerController.allowsEditing = isEditable self.imagePickerController.mediaTypes = ["public.image", "public.movie"] } /// Presents the image picker to the user. /// /// This method checks for camera and photo library permissions and then presents an alert /// allowing the user to choose between the camera or photo roll, or to grant permissions if needed. public func openImagePicker(){ var cameraPerm: Bool = false var albumPerm: Bool = false //Camera if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == AVAuthorizationStatus.authorized { // Already Authorized cameraPerm = true } else { cameraPerm = false } //Photos let photos = PHPhotoLibrary.authorizationStatus() if photos == .authorized { albumPerm = true }else if photos == .notDetermined || photos == .denied || photos == .restricted { albumPerm = false } DispatchQueue.main.async { if let presentedController = (self.delegate as? UIViewController)?.presentedViewController, presentedController == self { self.openAlerts(forCamera: cameraPerm, forAlbum: albumPerm) }else{ (self.delegate as? UIViewController)?.present(self, animated:false, completion: { self.openAlerts(forCamera: cameraPerm, forAlbum: albumPerm) }) } } } /// Presents an `UIAlertController` with options to open the camera, photo roll, or grant permissions. /// - Parameters: /// - forCamera: A boolean indicating whether camera access is granted. Defaults to `true`. /// - forAlbum: A boolean indicating whether photo album access is granted. Defaults to `true`. private func openAlerts(forCamera:Bool = true, forAlbum:Bool = true){ let alert = UIAlertController(title: "Choose an option", message: nil, preferredStyle: .actionSheet) if forCamera { alert.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in self.openCameraDevice() })) }else{ alert.addAction(UIAlertAction(title: "Allow camera permission", style: .default, handler: { _ in self.openAppSettingsCamera() })) } if forAlbum { alert.addAction(UIAlertAction(title: "Camera roll", style: .default, handler: { _ in self.openAlbumDevice() })) }else{ alert.addAction(UIAlertAction(title: "Allow camera roll permission", style: .default, handler: { _ in self.openAppSettingsPhoto() })) } alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: { _ in self.dismiss(animated: false, completion: nil) })) self.isAlertOpen = true DispatchQueue.main.async { self.modalPresentationStyle = .fullScreen self.present(alert, animated: true, completion: nil) } } /// Opens the camera device using `UIImagePickerController`. /// /// If the camera is not available, an alert message is presented to the user. private func openCameraDevice(){ if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera)){ self.imagePickerController.sourceType = UIImagePickerController.SourceType.camera self.modalPresentationStyle = .fullScreen self.present(self.imagePickerController, animated: true, completion: nil) }else{ let alert = UIAlertController(title: "Warning", message: "You don't have camera", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) self.modalPresentationStyle = .fullScreen self.present(alert, animated: true, completion: nil) } } /// Opens the photo library using `UIImagePickerController`. private func openAlbumDevice(){ self.imagePickerController.sourceType = UIImagePickerController.SourceType.photoLibrary self.modalPresentationStyle = .fullScreen self.present(self.imagePickerController, animated: true, completion: nil) } /// Requests photo library access and if denied, guides the user to app settings. private func openAppSettingsPhoto(){ PHPhotoLibrary.requestAuthorization({status in if status == .authorized{ if self.isAlertOpen { self.isAlertOpen = false self.openImagePicker() } }else{ guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } if UIApplication.shared.canOpenURL(settingsUrl) { if #available(iOS 10.0, *) { UIApplication.shared.open(settingsUrl, completionHandler: { (success) in }) } else { UIApplication.shared.openURL(settingsUrl) } } } }) } /// Requests camera access and if denied, guides the user to app settings. private func openAppSettingsCamera(){ AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted: Bool) -> Void in if granted == true { if self.isAlertOpen { self.isAlertOpen = false self.openImagePicker() } } else { guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } if UIApplication.shared.canOpenURL(settingsUrl) { if #available(iOS 10.0, *) { UIApplication.shared.open(settingsUrl, completionHandler: { (success) in }) } else { UIApplication.shared.openURL(settingsUrl) } } } }) } } //MARK: - UIImagePicker Delegate extension ImagePickerController: UIImagePickerControllerDelegate { /// Tells the delegate that the user canceled the pick operation. /// - Parameter picker: The image picker controller. @objc public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { self.delegate?.imagePicker(didSelect: nil) picker.dismiss(animated: true, completion: { self.dismiss(animated: false, completion: nil) }) } /// Tells the delegate that the user picked an image or movie. /// - Parameters: /// - picker: The image picker controller. /// - info: A dictionary containing the original image, and possibly an edited image or a movie URL. @objc public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { self.delegate?.imagePicker(didSelect: image) }else if let image = info[ UIImagePickerController.InfoKey.originalImage] as? UIImage { self.delegate?.imagePicker(didSelect: image) } picker.dismiss(animated: true, completion: { self.dismiss(animated: false, completion: nil) }) } } #endif