We have a photo picker made with UIImagePickerController.
When making double tap (instead of one tap) the photo from gallery.
On iOS 10: UIImagePickerController is dismissed
On iOS 11: UIImagePickerController is dismissed and presenting view controller is dismissed as well :0
Is it iOS 11 bug or we have to adjust something?
Our code:
let vc = UIImagePickerController()
vc.delegate = self
vc.modalPresentationStyle = .overFullScreen
vc.allowsEditing = false
rootVC.present(vc, animated: true) // `rootVC` also presented modally.
Instead of self.dismiss(), use picker.dismiss()
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
//self.dismiss(animated: true, completion: nil)
picker.dismiss(animated: true, completion: nil)
}
This will only dismiss your picker view and not the view controller.
We end up with solution: Set delegate = nil right after delegate call in didFinishPickingMediaWithInfo.
public class ImagePicker: NSObject {
private lazy var viewController = setupActionSheet()
private var rootViewController: UIViewController?
private var completionHandler: (([String: Any]) -> Void)?
private var cancellationHandler: (() -> Void)?
}
extension ImagePicker {
public func present(on: UIViewController, completionHandler: #escaping (([String: Any]) -> Void)) {
rootViewController = on
self.completionHandler = completionHandler
cancellationHandler = nil
on.presentAnimated(viewController)
}
public func present(on: UIViewController,
completionHandler: #escaping (([String: Any]) -> Void),
cancellationHandler: #escaping (() -> Void)) {
rootViewController = on
self.completionHandler = completionHandler
self.cancellationHandler = cancellationHandler
on.presentAnimated(viewController)
}
}
extension ImagePicker {
private func setupActionSheet() -> UIAlertController {
let actionSheet = UIAlertController(actionSheetWithTitle: LocalizedString.Generic.ImagePicker.addPhoto)
if UIImagePickerController.isSourceTypeAvailable(.camera) {
actionSheet.addDefaultAction(LocalizedString.Generic.ImagePicker.takePhoto) { [weak self] _ in
self?.presentImagePicker(.camera)
}
}
actionSheet.addDefaultAction(LocalizedString.Generic.ImagePicker.selectPhoto) { [weak self] _ in
self?.presentImagePicker(.photoLibrary)
}
actionSheet.addCancelAction(LocalizedString.Generic.ButtonTitle.cancel) { [weak self] _ in
self?.cancellationHandler?()
}
return actionSheet
}
private func presentImagePicker(_ sourceType: UIImagePickerControllerSourceType) {
let vc = UIImagePickerController()
vc.delegate = self
vc.allowsEditing = false
vc.sourceType = sourceType
rootViewController?.present(vc, animated: true)
}
}
extension ImagePicker: UIImagePickerControllerDelegate {
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
picker.delegate = nil /// It prevents to call delegate when user taps on a few images very fast. seems iOS 11 issue only.
if picker.sourceType == .camera, let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
picker.dismiss(animated: true) {
self.completionHandler?(info)
}
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true) {
self.cancellationHandler?()
}
}
}
Usage:
// Somewhere in view controller code.
imagePicker = ImagePicker()
imagePicker?.present(on: self) { [weak self] in
self?.imagePicker = nil
self?.viewModel.addImage($0)
}
Related
I wrote a UIViewControllerRepresentable for a VideoRecordingView in SwiftUI:
import SwiftUI
import AVFoundation
import Photos
struct VideoRecordingView: UIViewControllerRepresentable {
#Binding var videoURL: URL?
#Environment(\.viewController) private var viewControllerHolder: UIViewController?
let imagePickerController: UIImagePickerController
init(videoURL: Binding<URL?>) {
self._videoURL = videoURL
imagePickerController = UIImagePickerController()
}
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoRecordingView>) -> UIImagePickerController {
imagePickerController.allowsEditing = true
imagePickerController.sourceType = .camera
imagePickerController.mediaTypes = ["public.movie"]
imagePickerController.videoMaximumDuration = .infinity
if #available(iOS 14.0, *) {
imagePickerController.videoQuality = .typeHigh
} else {
}
imagePickerController.delegate = context.coordinator
return imagePickerController
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<VideoRecordingView>) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: VideoRecordingView
init(_ parent: VideoRecordingView) {
self.parent = parent
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { notification in
parent.imagePickerController.stopVideoCapture()
// parent.viewControllerHolder?.dismiss(animated: true, completion: nil)
}
}
func requestAuthorizationToPhotos(completionHandler: #escaping (Bool) -> Void) {
guard PHPhotoLibrary.authorizationStatus() != .authorized else {
completionHandler(true)
return
}
PHPhotoLibrary.requestAuthorization { status in
completionHandler(status == .authorized ? true : false)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.stopVideoCapture()
parent.viewControllerHolder?.dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.stopVideoCapture()
if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
DispatchQueue.global(qos: .background).async {
let fileURL = videoURL.copyFileToTempDirectory()
DispatchQueue.main.async {
self.parent.videoURL = fileURL
}
self.requestAuthorizationToPhotos { granted in
if (granted && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(videoURL.path)) {
UISaveVideoAtPathToSavedPhotosAlbum(videoURL.path, nil, nil, nil)
}
}
}
}
parent.viewControllerHolder?.dismiss(animated: true, completion: nil)
}
}
}
I use imagePickerController.stopVideoCapture() and dismiss it, when I close it.
When I close it, I exit the app and go back to the app again. (I go to the background and return to the foreground.)
At this time, a green light appears in the status bar, which means it is using the camera. While it is closed and I do not know which part of my code is using the camera.
Please help me. Thanks :)
I'm working with WKWebView on which I'm tapping 'Load file'. WKWebView opening for me UIImagePickerView during which I need to know - what image was choosen.
Worked for me.
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let imagePicker = viewControllerToPresent as? UIImagePickerController {
previousIPDelegate = imagePicker.delegate
imagePickerController = imagePicker
imagePickerController?.delegate = self
}
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
After this - you'll be able to work with didFinishPickingMediaWithInfo func where you can catch and work with chosen media, should return old delegate to imagePicker and call didFinishPickingMediaWithInfo func in old delegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var newInfo = info
if let pickedImage = newInfo[UIImagePickerControllerOriginalImage] as? UIImage {
/// DO YOUR STUFF HERE
}
picker.delegate = previousIPDelegate
previousIPDelegate?.imagePickerController!(picker, didFinishPickingMediaWithInfo: newInfo)
}
You should do the same in DidCancel func
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.delegate = previousIPDelegate
previousIPDelegate?.imagePickerControllerDidCancel!(picker)
}
How do i prevent users from picking the same image twice in UIImagePickerContoroller to avoid duplication?
I tried doing it with the URLReference but its not working so I'm guessing its not the way.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let url = info[UIImagePickerControllerReferenceURL] as? NSURL{
if photosURL.contains(url){
Utilities.showMessage(message: "photo Uploaded already", sender: self, title: ErrorTitle.FRIENDS, onDismissAction: nil)
} else {
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
photos.append(pickedImage)
}
}
}
dismiss(animated: true, completion: nil)
}
thanks,
You should also consider doing the picker.dismiss first and do the other logic with the image afterward. That way, you can prevent the user from tapping an image multiple times and invoking the delegate function several times.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard !picker.isBeingDismissed else {
return
}
picker.dismiss(animated: true) {
if let pickedImage = (info[UIImagePickerController.InfoKey(rawValue: UIImagePickerController.InfoKey.originalImage.rawValue)] as? UIImage) {
// do stuff with the picked image
print("Uesr picked an image \(pickedImage)")
}
}
}
swift 4
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
if let capturedImage = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.originalImage)] as? UIImage {
// do stuff
}
picker.delegate = nil
picker.dismiss(animated: true, completion: nil)
}
Seems like you haven't appended the url to the photosURL?
try this out:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let url = info[UIImagePickerControllerReferenceURL] as? NSURL{
if photosURL.contains(url){
Utilities.showMessage(message: "photo Uploaded already", sender: self, title: ErrorTitle.FRIENDS, onDismissAction: nil)
} else {
photosURL.append(url)
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
photos.append(pickedImage)
}
}
}
dismiss(animated: true, completion: nil)
}
preselectedAssetIdentifiers
If you have chosen PHPicker while creating a gallery picker in an application you created with SwiftUI, and you want to prevent this picker from selecting the selected photos over and over again, you can apply the following method.
The feature that will prevent the user from selecting the same photo twice is "preselectedAssetIdentifiers"
import Foundation
import PhotosUI
import SwiftUI
struct GalleryPicker: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return GalleryPicker.Coordinator(parent: self)
}
#Binding var images: [UIImage]
#Binding var picker: Bool
#Binding var preSelected: [String]
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.preselectedAssetIdentifiers = preSelected
configuration.filter = .images
configuration.selection = .ordered
configuration.selectionLimit = 0
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator.self
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: GalleryPicker
init(parent: GalleryPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.picker.toggle()
for result in results {
let provider = result.itemProvider
let assetIdentifier = result.assetIdentifier ?? ""
print("asset --> \(assetIdentifier)")
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
DispatchQueue.main.async {
self.parent.preSelected.append(assetIdentifier)
self.parent.images.append(image as! UIImage)
}
}
}
}
}
}
}
screenshot
class CameraPicker: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
weak var viewController:MyProfileVC!
func launchCamera() {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
let imagePicker:UIImagePickerController = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.front
imagePicker.cameraCaptureMode = .photo
imagePicker.allowsEditing = false
self.viewController.present(imagePicker, animated: true, completion: nil)
} }
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
print("didFinishPickingMedia")
}
This is my object class function, but 'didFinishPickingMediaWithInfo' function doesn't get called after taking the picture. Also, the viewcontroller which is presenting the imagepicker is a different Swift file
I had the same problem and I've found a solution, so I'm posting my version (I'm taking a picture from the photo library but it's the same :) ).
I had a memory management issue.
I've created an IBAction function where I instantiated my camera handler class (with the delegate inside...). At the end of the function the variable goes out of scope and it's deallocated. To solve the issue I've made it as instance variable.
That's my code for the VC with my UiButton:
class STECreateUserVC: UIViewController {
#IBOutlet weak var imgAvatar: UIImageView!
let cameraHandler = STECameraHandler()
#IBAction func buttonPressed(_ sender: UIButton) {
cameraHandler.importPictureIn(self) { [weak self] (image) in
self?.imgAvatar.image = image
}
}
}
...and that's my handler:
class STECameraHandler: NSObject {
let imagePickerController = UIImagePickerController()
var completitionClosure: ((UIImage) -> Void)?
func importPictureIn(_ viewController: UIViewController, completitionHandler:((UIImage) -> Void)?) {
completitionClosure = completitionHandler
imagePickerController.delegate = self
imagePickerController.allowsEditing = true
imagePickerController.sourceType = .photoLibrary
imagePickerController.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
viewController.present(imagePickerController, animated: true, completion: nil)
}
}
extension STECameraHandler: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let completitionClosure = completitionClosure, let image = info[UIImagePickerControllerEditedImage] as? UIImage {
completitionClosure(image)
}
imagePickerController.dismiss(animated: true)
}
}
I've used a closure in order to have a cleaner code.
This:
self.viewController.present(imagePicker, animated: true, completion: nil)
should be:
self.present(imagePicker, animated: true, completion: nil)
I Have a circular framed UIImageView and I need to add a circular framed cropping tool for the UIImagePickerController, after the image is selected from a photo library. Very similar to Instagram's UIImagePicker's crop component. How do I add this type of component?
UPDATE
I've found this repo with a circular cropping tool https://github.com/ruslanskorb/RSKImageCropper
but can someone guide me on to how to implement this cropping tool with the UIImagePickerController after the user selects a photo from the photo library?
UPDATE
I am getting the following message in my debugger :
and the buttons in my crop view are disabled, meaning I cannot select them.. what message is the debugger relaying on to me?
here is my code:
#IBAction func chooseProfilePicture(sender: AnyObject) {
var myPickerController = UIImagePickerController()
myPickerController = UIImagePickerController()
myPickerController.delegate = self;
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(myPickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
var image : UIImage = (info[UIImagePickerControllerOriginalImage] as? UIImage)!
editProfilePictureImageView.image = image
self.dismissViewControllerAnimated(false, completion: { () -> Void in
var imageCropVC : RSKImageCropViewController!
imageCropVC = RSKImageCropViewController(image: image, cropMode: RSKImageCropMode.Circle)
imageCropVC.delegate = self
self.navigationController?.pushViewController(imageCropVC, animated: true)
})
}
Example Demo
Yes you can add RSKImageCropper in your UIImagePickerController
define imagePicker
var imagePicker : UIImagePickerController!
in ViewDidLoad
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary;
self.presentViewController(imagePicker, animated: true, completion: nil)
Delegate methode :
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject])
{
var image : UIImage = (info[UIImagePickerControllerOriginalImage] as? UIImage)!
picker.dismissViewControllerAnimated(false, completion: { () -> Void in
var imageCropVC : RSKImageCropViewController!
imageCropVC = RSKImageCropViewController(image: image, cropMode: RSKImageCropMode.Circle)
imageCropVC.delegate =self
self.navigationController?.pushViewController(imageCropVC, animated: true)
})
}
see :
Kirit Modi's answer was exactly what I needed, although I needed to do one thing to make this work. For those that dont download the test project, make sure to implement your delegate methods:
Swift 3:
extension YourViewControllerClass: RSKImageCropViewControllerDelegate {
func imageCropViewControllerDidCancelCrop(_ controller: RSKImageCropViewController) {
_ = self.navigationController?.popViewController(animated: true)
}
func imageCropViewController(_ controller: RSKImageCropViewController, didCropImage croppedImage: UIImage, usingCropRect cropRect: CGRect) {
self.avatarImageView.image = croppedImage
_ = self.navigationController?.popViewController(animated: true)
}
}
Swift 2:
extension YourViewControllerClass: RSKImageCropViewControllerDelegate {
func imageCropViewControllerDidCancelCrop(controller: RSKImageCropViewController) {
self.navigationController?.popViewControllerAnimated(true)
}
func imageCropViewController(controller: RSKImageCropViewController, didCropImage croppedImage: UIImage, usingCropRect cropRect: CGRect) {
self.avatarImageView.image = croppedImage
self.navigationController?.popViewControllerAnimated(true)
}
}
For Swift 2.2:
add delegate methods to your class:
class ViewController: UIViewController, UIImagePickerControllerDelegate, RSKImageCropViewControllerDelegate, UINavigationControllerDelegate
define imagePicker
var imagePicker : UIImagePickerController!
in viewDidLoad()
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(imagePicker, animated: true, completion: nil)
And delegate methode :
func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: NSDictionary!) {
let image : UIImage = image
picker.dismissViewControllerAnimated(false, completion: { () -> Void in
var imageCropVC : RSKImageCropViewController!
imageCropVC = RSKImageCropViewController(image: image, cropMode: RSKImageCropMode.Circle)
imageCropVC.delegate = self
self.navigationController?.pushViewController(imageCropVC, animated: true)
})
}
Of course it need to do bridge for objective C class
Oh, link to RSKImageCrop