Swift - get sender of UIImagePickerController - swift

I have 2 UIImageViews which I have added a UITapGestureRecognizer to which opens the user's camera. Once the user has selected an image, then the UIImageView that was tapped in order to access the camera should be set with the camera image.
So here is my openCamera method:
#objc func openCamera(sender: ImagePlaceHolderView) {
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.delegate = self
picker.sourceType = .camera
present(picker, animated: true)
}
My UIImageViews are declared as follows:
var left = UIImageView()
var right = UIImageView()
left = createImagePlaceholder()
right = createImagePlaceholder()
private func createImagePlaceholder() -> UIImageView {
let placeholder = ImagePlaceHolderView(frame: CGRect(x: 0, y: 0, width: 90, height: 150))
placeholder.isUserInteractionEnabled = true
return placeholder
}
And then my delegate method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else { return }
left.image = image // Here I want to set the sender's image to image
dismiss(animated: true, completion: nil)
}
I'm struggling to figure out how to set the image of the UIImageView that was tapped to this new image from the camera. I'm trying to pass the sender into the openCamera method but not sure how to extract this, as the sender seems in fact to be the UITapGestureRecognizer rather than the image that was tapped.

You'll have to store a reference to the sender and use that to set the image in the delegate method.
var imagePlaceholderView: ImagePlaceHolderView? // set the reference
#objc func openCamera(sender: UITapGestureRecognizer) {
imagePlaceholderView = sender.view as? ImagePlaceHolderView
//...
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else { return }
imagePlaceholderView?.image = image
dismiss(animated: true, completion: nil)
}

Related

Select Video Instead of Image using ImagePickerController - Swift

My situation is that I have a working imagepickercontroller that allows the user to pick an image from their camera roll and display it on an imageview inside the application.
The problem is that I also want to be able to do the same thing with videos, and instead, display the video on an avplayer. I've done some research but couldn't find any good sources.
Can someone show me how to do this? possibly by editing the code below?
Thanks in advance!
This is the code I used for importing and displaying images from cameraroll (all above the viewDidLoad()):
#IBOutlet weak var imageView: UIImageView!
// the image picker controller
var imagePicker = UIImagePickerController()
// this is the button you tap to import your photo
#IBAction func imageViewButtonTapped(_ sender: Any) {
if UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) {
imagePicker.delegate = self
imagePicker.allowsEditing = true
present(imagePicker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info[.editedImage] as? UIImage{
selectedImageFromPicker = editedImage
}else if let originalImage = info[.originalImage] as? UIImage{
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
imageView.image = selectedImage
}
dismiss(animated: true, completion: nil)
}
try this:
import AVFoundation
class VideoHelper {
static func startMediaBrowser(delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate, sourceType: UIImagePickerController.SourceType) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType) else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
}
and you can use this as:
let source = UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) ? UIImagePickerController.SourceType.camera : UIImagePickerController.SourceType.savedPhotosAlbum
VideoHelper.startMediaBrowser(delegate: self, sourceType: source)

How to modify imagePickerController (source camera) not to dismiss after pressing "Use photo" until 4 photos are taken?

I want to take 4 photos before UIImagePickerController is dismissed. How to modify it?
I have following button action and delegate:
#IBAction func take4Photos(_ sender: Any) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let image = UIImagePickerController()
image.delegate = self
image.sourceType = .camera;
image.allowsEditing = false
self.present(image, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage{
imagePicked = image
self.dismiss(animated: true, completion: nil)
}
I will recommend you to use ImagePicker, which can be easily installed through cocoaPods and it can fulfill your requirement with just one line of Code.
let imagePickerController = ImagePickerController()
imagePickerController.imageLimit = 4
You can check ImagePicker library here
Here's how you can use ImagePicker library to pick multiple images.
Create a button in Storyboard and have its outlet in the ViewController.
#IBOutlet weak var chooseImage: UIButton!
If you want to see the selected images in VC after selection, make an array of UIImages like this
var imageViews:[UIImageView] = []
Then, in viewDidLoad, add target to chooseImage button
chooseImage.addTarget(self, action: #selector(buttonTouched(button:)), for: .touchUpInside)
And declare buttonTouched function outside the viewDidLoad like this
#objc func buttonTouched(button: UIButton) {
let config = Configuration()
config.doneButtonTitle = "Done"
config.noImagesTitle = "Sorry! There are no images here!"
config.recordLocation = false
config.allowVideoSelection = false. //If you don't want video recording
let imagePicker = ImagePickerController(configuration: config)
imagePicker.delegate = self
present(imagePicker, animated: true, completion: nil)
}
Then, in the end, inside the extension, conform to ImagePicker delegate functions like this.
extension OwnerAddListingFacilitiesViewController:ImagePickerDelegate {
func cancelButtonDidPress(_ imagePicker: ImagePickerController) {
imagePicker.dismiss(animated: true, completion: nil)
}
func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) { //Don't know what exactly this function does }
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
for Count in 0..<images.count {
let imageView = UIImageView(image: images[Count])
imageView.frame = CGRect(x: (0 * (110 * Count)), y: 0, width: 50, height: 50)
imageViews.append(imageView)
}
imagePicker.dismiss(animated: true, completion: nil)
}
}
You'll be able to see small thumbnail like images after selecting them and pressing done.
Unfortunately, that is not possible. UIImagePickerController can pick only one media. If you want that behaviour, either you have to create your own picker or use a library.
If you want to create your own, use this resource
https://developer.apple.com/documentation/photokit/browsing_and_modifying_photo_albums
It has an official demo source from which you can create your own within 2-3 hours (I had to do this exact problem once, multiple image and video selection from a grid like selector)

UIImagePickerController didFinishPickingMediaWithInfo not being called with cameraOverlyView

I have UIImagePickerController with cameraOverlyView. I added tap gesture recognizer to the overlay view - when user taps on the view the recording should be stopped and saved:
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = self
//customView stuff
let customViewController = CustomOverlayViewController(
nibName:"CustomOverlayViewController",
bundle: nil
)
let customView:CustomOverlayView = customViewController.view as! CustomOverlayView
customView.frame = delegate.view.frame //self.picker.view.frame
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(cameraViewTapped(tapGestureRecognizer:)))
customView.cameraView.addGestureRecognizer(tapGestureRecognizer)
customView.delegate = delegate
delegate.present(mediaUI, animated: true, completion: {
mediaUI.cameraOverlayView = customView
})
gesture recognizer calls this:
#objc func cameraViewTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
if isRecording {
self.picker!.stopVideoCapture()
self.recordButton.isEnabled = false
print("stopped")
isRecording = false
} else {
self.picker!.startVideoCapture()
print("started")
isRecording = true
}
}
delegate method:
extension HomeViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
print("didFinishPickingMediaWithInfo")
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerControllerMediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerControllerMediaURL] as? URL,
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
else {
return
}
// Handle a movie capture
UISaveVideoAtPathToSavedPhotosAlbum(
url.path,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
}
My question is how to get recording from the picker controller? Delegate method didFinishPickingMediaWithInfo is not called because of the overlay view: UIImagePickerController didFinishPickingMediaWithInfo not being called when cameraOverlyView property is assigned
Solved the issue by adding the following line of code:
mediaUI.showsCameraControls = false

Show a view in front of the imagePicker

In my application the user can either open the camera roll to select a picture or open the camera to take directly one by himself.
In both cases, the picture selected/taken will also be saved locally for further reference.
The downside is that the saving operation usually freeze the screen until it is finished.
I found an animation in this post and I want to display it in front of the imagePickerController but I can't manage to do so.
class SinglePageViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UINavigationBarDelegate {
var spinner: UIActivityIndicatorView?
lazy var showCameraImagePickerController: UIImagePickerController = {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.allowsEditing = false
return imagePicker
}()
lazy var showPhotoImagePickerController: UIImagePickerController = {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = false
return imagePicker
}()
#IBOutlet weak var photoButton: UIButton!
#IBAction func onPhotoButton(_ sender: Any) {
self.present(self.showCameraImagePickerController, animated: true, completion: nil)
}
#IBOutlet weak var galleryButton: UIButton!
#IBAction func onGalleryButton(_ sender: Any) {
self.present(self.showPhotoImagePickerController, animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
//start animation
let screenSize: CGRect = UIScreen.main.bounds
spinner = UIActivityIndicatorView(frame: CGRect(x: screenSize.width / 2 - 150, y: screenSize.height / 2 - 150, width: 300, height: 300))
spinner?.isHidden = false
spinner?.startAnimating()
spinner?.color = UIColor.red
switch picker {
case showCameraImagePickerController:
// snap pic, save to doc, save to album
self.showCameraImagePickerController.view.addSubview(spinner!)
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false, block: { _ in
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
if self.saveImage(imageName: "\(self.titleLabel.text!).png", image: image) {
// additionally save to photo album
UIImageWriteToSavedPhotosAlbum(image!, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
print("saved \(self.titleLabel.text!).png")
self.imageView.image = image
}
})
case showPhotoImagePickerController:
//switch pic, save to doc. no album
self.showPhotoImagePickerController.view.addSubview(spinner!)
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false, block: { _ in
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
if self.saveImage(imageName: "\(self.titleLabel.text!).png", image: image) {
print("saved new \(self.titleLabel.text!).png")
self.imageView.image = image
self.spinner?.stopAnimating()
self.spinner?.removeFromSuperview()
self.spinner = nil
self.showPhotoImagePickerController.dismiss(animated: true, completion: nil)
} else {
self.spinner?.stopAnimating()
self.spinner?.removeFromSuperview()
self.spinner = nil
self.showPhotoImagePickerController.dismiss(animated: true, completion: nil)
}
})
default:
return
}
}
#objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
spinner?.stopAnimating()
spinner?.removeFromSuperview()
spinner = nil
self.showCameraImagePickerController.dismiss(animated: true, completion: nil)
}
func saveImage(imageName: String, image: UIImage?) -> Bool {
//create an instance of the FileManager
let fileManager = FileManager.default
//get the image path
let imagePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(imgDir + imageName)
print(imagePath)
//get the image we took with camera
let image = rotateImage(image: image!)
//get the PNG data for this image
let data = UIImagePNGRepresentation(image)
//store it in the document directory
if fileManager.createFile(atPath: imagePath as String, contents: data, attributes: nil) {
newItem?.image = true
return true
} else {
print("error while saving")
return false
}
}
}
as you can see I tried playing with bringSubView(toFront:) and also with the zPosition but with no results.
following this similar question I looked into the documentation for cameraOverlayView but it says that it only works when the imagePicker is presented in camera mode, which doesn't cover the case when I open the photo library
I also recently tried to use a workaround, meaning that I dismiss the imagePickerController as soon as possible and update the image afterwards, but that is not optimal anymore because of some changes in the structure of app.
EDIT
to make myself clearer I'll state again what I need: show the spinner animation in front of the imagePicker, as soon as I tap a photo to choose it, and until I finish saving, then dismiss the imagePicker.
I do not want to first dismiss the picker and then save while showing the spinner in the main view.
EDIT2
updated the code with the new one from the answer. only problem is that if I don't put a timer the spinner shows itself only at the end of the saving process for a brief moment (checked with breakpoints).
This results in no animation during the couple of seconds of saving process and just a brief apparition of the spinner at the end before dismissing the imagePicker.
Just putting a 0.1sec delay triggers the spinner immediately and I get the expected behaviour (animation while saving).
No idea why
Please see a complete example where spinner will show while the image is being saved and once finishes saving spinner will be removed.
class ViewController: UIViewController {
/// Image picker controller
lazy var imagePickerController: UIImagePickerController = {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary;
imagePicker.allowsEditing = false
return imagePicker
}()
var spinner: UIActivityIndicatorView?
#IBAction func imagePickerButton(_ sender: UIButton) {
self.present(self.imagePickerController, animated: true, completion: nil)
}
}
// MARK: ImagePicker Delegate to get the image picked by the user
extension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
//start animation
let screenSize: CGRect = UIScreen.main.bounds
spinner = UIActivityIndicatorView(frame: CGRect(x: screenSize.width/2 - 50, y: screenSize.height/2 - 50, width: 100, height: 100))
spinner?.isHidden = false
spinner?.startAnimating()
spinner?.color = UIColor.black
self.imagePickerController.view.addSubview(spinner!)
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
UIImageWriteToSavedPhotosAlbum(image!, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
#objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
spinner?.stopAnimating()
spinner?.removeFromSuperview()
spinner = nil
self.imagePickerController.dismiss(animated: true, completion: nil)
if error == nil {
let ac = UIAlertController(title: "Saved!", message: "Your altered image has been saved to your photos.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}

Set image of UIImageView using gestureRecognizer

I am asking if it is possible to set the image of a UIImageView using a gestureRecognizer, or is it necessary to overlay a button on top of the UIImageView.
I have an outlet collection of UIImageViews, called imageViews. There are four of these, and their tags have been set from 1 to 4. In my viewDidLoad, to add the gesture recognizer to each of the image views in the collection, i have used:
override func viewDidLoad() {
super.viewDidLoad()
for i in (0..<imageViews.count) {
let imageViewTapped = UITapGestureRecognizer(target: self, action: #selector(selectImage(tap:)))
imageViewTapped.numberOfTapsRequired = 1
imageViews[i].addGestureRecognizer(imageViewTapped)
}
}
Next, I created my gestureRecognizer as a function in the viewController class. This is where the image picker controller is created and where the image view that was tapped is identified:
func selectImage(tap: UITapGestureRecognizer) {
var imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = false
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
self.present(imagePicker, animated: true, completion: nil)
guard let viewTappedTag = tap.view?.tag else {return}
self.selectedImageView = imageViews[viewTappedTag - 1]
}
selectedImageView is a variable in the viewController class with a type of UIImageView. My thinking was that this variable could hold the UIImageView that was tapped, which is identified by the gestureRecognizer, as shown above. This could then be passed to the delegate later on.
Next, I created the delegates, the didFinishPickingMediaWithInfo and didCancel, though I will only show the former for brevity. Both were created inside the viewController class:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var chosenImage = UIImage()
chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.selectedImageView.image = chosenImage
dismiss(animated: true, completion: nil)
}
Unfortunatley, the image of the image view that was tapped is not updated to the one that was chosen from the library. What is the correct way to do this? And I feel as though I am bloating my viewController with all of this code, can this be moved to a seperate class? Am i wrong in doing it like this, and instead one should just overlay a button?
EDIT
The best I could come up with so far is a switch statement in my imagePickerController, where imageViewOne, imageViewTwo etc are outlets for each UIImageView:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var chosenImage = UIImage()
chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
switch self.viewTappedTag {
case 1:
imageViewOne.image = chosenImage
case 2:
imageViewTwo.image = chosenImage
case 3:
imageViewThree.image = chosenImage
case 4:
imageViewFour.image = chosenImage
default:
imageViewOne.image = chosenImage
}
dismiss(animated: true, completion: nil)
}
where viewTappedTag is a viewController class variable, the value of which is defined by the tag of the view that was tapped and is set in the gestureRecognizer. Can anyone improve on this?
Update the code as following
Declare a variable in the class as follows
var selectedImageViewIndex = 0 //by default selected image view is 0th image view
func selectImage(tap: UITapGestureRecognizer) {
var imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = false
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
self.present(imagePicker, animated: true, completion: nil)
guard let viewTappedTag = tap.view?.tag else {return}
self.selectedImageView = imageViews[viewTappedTag - 1]
//Add the following line
self.selectedImageViewIndex = viewTappedTag - 1
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var chosenImage = UIImage()
chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.selectedImageView.image = chosenImage
//Add the following line
self.imageView[self.selectedImageViewIndex].image = chosenImage
dismiss(animated: true, completion: nil)
}
Though I would advice not to use viewTappedTag - 1 and instead get the value of index and tapped image tag from a mapped object.
In the end I used a switch statement to achieve my goal:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var chosenImage = UIImage()
chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
switch self.viewTappedTag {
case 1:
imageViewOne.image = chosenImage
case 2:
imageViewTwo.image = chosenImage
case 3:
imageViewThree.image = chosenImage
case 4:
imageViewFour.image = chosenImage
default:
imageViewOne.image = chosenImage
}
dismiss(animated: true, completion: nil)
}