I'm creating an app that will capture a photo and reorient it based on the gyroscope data at the time of photo capture.
I've created functions to capture the gyro data from the phone in:
startUpdates()
and to stop capturing the data in:
stopUpdates()
I tried adding this into the UIImagePickerController:
if UIImagePickerController.isSourceTypeAvailable(.camera) {
guard ptInitials.text != "" else {
missingIdentifier(text: "Please add an identifier prior to taking a photo")
return}
startUpdates()
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = false
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.cameraCaptureMode = .photo
imagePicker.modalPresentationStyle = .fullScreen
present(imagePicker,
animated: true,
completion: nil)
}
This starts the gyro capture as the image capture process begins.
I have it passing the gyro data to an optional double, livePhotoAxis: Double?, while it is measuring this inside the "startUpdates()" function.
I was attempting to get it to "stop" capturing the data once the picture is taken so the last known gyro data would be kept in my variable and able to pass into other functions;
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
stopUpdates()
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
saveImageToDocuments(image: chosenImage, fileNameWithExtension: "image.jpg")
dismiss(animated: true, completion: nil
)
}
However, the problem is that the stopUpdates() method isn't called until after the user "confirms" they like their snapped photo (vs retaking it).
From what I've read, there is a private API with uiImagePickerControllerUserDidCaptureItem that captures the exact moment the photo is taken. I could use NSNotification to try and find this and call StopUpdates() once the photo is actually taken.
Is this correct that this is a private API? Can I use that to capture this moment or will my app get rejected?
Likewise, is there a better way to turn off my gyro updates at the exact moment of photo capture?
Thanks!
I added this code and it fixed the problem, but I still don't know if this is a private API and will be allowed in the app store or not? Or if there is a better way to accomplish this?
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { note in
self.stopUpdates()
})
Update: This doesn't fire the EXACT moment of photo capture. It fires when the photo is "locked in". There are a few seconds (2ish) between the time the take photo button is pressed and the photo locks into place. As a result, if you move the phone during that time, the gyro data will be inaccurate.
Any thoughts on how to improve on this?
Related
I have been unable to capture Live Photos using UIImagePickerController. I can capture still photos and even video (which is not my scenario but I checked just to make sure), but the camera does not capture live photos. The documentation suggests it should (source):
To obtain the motion and sound content of a live photo for display (using the PHLivePhotoView class), include the kUTTypeImage and kUTTypeLivePhoto identifiers in the allowed media types when configuring an image picker controller. When the user picks or captures a Live Photo, the editingInfo dictionary contains the livePhoto key, with a PHLivePhoto representation of the photo as the corresponding value.
I've set up my controller:
let camera = UIImagePickerController()
camera.sourceType = .camera
camera.mediaTypes = [UTType.image.identifier, UTType.livePhoto.identifier]
camera.delegate = context.coordinator
In the delegate I check for the Live Photo:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let live = info[.livePhoto] as? PHLivePhoto {
// handle live photo
} else if let takenImage = info[.originalImage] as? UIImage, let metadata = info[.mediaMetadata] as? [AnyHashable:Any] {
// handle still photo
}
}
But I never get the Live Photo.
I've tried adding NSMicrophoneUsageDescription to the info.plist thinking it needs permissions for the microphone, but that did not help. Of course, I've added the NSCameraUsageDescription to give camera permissions.
Has anyone successfully captured Live Photos using UIImagePickerController?
In my application I found a memory leak when I used the UIImagePickerController, I thought it was my application, but searching for a solution I found an Apple's sample and I also found that this sample has the same memory leak.
You can find the example in the following URL.
https://developer.apple.com/library/content/samplecode/PhotoPicker/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010196
According to the UIImagePickerController documentation:
https://developer.apple.com/documentation/uikit/uiimagepickercontroller
In point 5, thy said that you have to dismiss the image picker using your delegate object, in the Apple's sample the UIImagePickerDelegate is doing the dismiss.
The issue is that the memory leak is wasting approximately 21 MB of memory when you select an image and work with it.
Used Memory without Memory Leak
Used Memory with Memory Leak
Memory Leak
This is the code to present the UIImagePickerController:
#IBAction func showImagePickerForPhotoPicker(_ sender: UIBarButtonItem) {
showImagePicker(sourceType: UIImagePickerControllerSourceType.photoLibrary, button: sender)
}
fileprivate func showImagePicker(sourceType: UIImagePickerControllerSourceType, button: UIBarButtonItem) {
// If the image contains multiple frames, stop animating.
if (imageView?.isAnimating)! {
imageView?.stopAnimating()
}
if capturedImages.count > 0 {
capturedImages.removeAll()
}
imagePickerController.sourceType = sourceType
imagePickerController.modalPresentationStyle =
(sourceType == UIImagePickerControllerSourceType.camera) ?
UIModalPresentationStyle.fullScreen : UIModalPresentationStyle.popover
let presentationController = imagePickerController.popoverPresentationController
presentationController?.barButtonItem = button // Display popover from the UIBarButtonItem as an anchor.
presentationController?.permittedArrowDirections = UIPopoverArrowDirection.any
if sourceType == UIImagePickerControllerSourceType.camera {
// The user wants to use the camera interface. Set up our custom overlay view for the camera.
imagePickerController.showsCameraControls = false
// Apply our overlay view containing the toolar to take pictures in various ways.
overlayView?.frame = (imagePickerController.cameraOverlayView?.frame)!
imagePickerController.cameraOverlayView = overlayView
}
present(imagePickerController, animated: true, completion: {
// Done presenting.
})
}
And this is the code in the delegate to dismiss the UIImagePickerController:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage]
capturedImages.append(image as! UIImage)
if !cameraTimer.isValid {
// Timer is done firing so Finish up until the user stops the timer from taking photos.
finishAndUpdate()
} else {
dismiss(animated: true, completion: nil)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: {
// Done cancel dismiss of image picker.
})
}
fileprivate func finishAndUpdate() {
dismiss(animated: true, completion: { [weak self] in
guard let `self` = self else {
return
}
if `self`.capturedImages.count > 0 {
if self.capturedImages.count == 1 {
// Camera took a single picture.
`self`.imageView?.image = `self`.capturedImages[0]
} else {
// Camera took multiple pictures; use the list of images for animation.
`self`.imageView?.animationImages = `self`.capturedImages
`self`.imageView?.animationDuration = 5 // Show each captured photo for 5 seconds.
`self`.imageView?.animationRepeatCount = 0 // Animate forever (show all photos).
`self`.imageView?.startAnimating()
}
// To be ready to start again, clear the captured images array.
`self`.capturedImages.removeAll()
}
})
}
I'm still looking for a solution, any help will be appreciated.
I had the same issue. So did a ton of other people. Dating back to 2008. Pretty crazy.
http://iphonedevsdk.com/forum/iphone-sdk-development/3816-uiimagepickercontroller-memory-issues.html
UIImagePickerController leaking memory after launch and during taking a picture. Makes app crash after taking more than a 100 pictures
UIImagePickerController Memory Leak
Unfortunately, the best answer I could find was to use a singleton, which sucks. [i.e. Intentionally retain an instance of UIImagePickerController so you just access that every single time you want to select an image.] This is what others have suggested.
Further, I just spent an hour writing this answer with code that uses a singleton, and I just could not avoid a memory leak [although I thought I had for a second]. Maybe I'm just doing it incorrectly - feel free to try. But I won't post my dysfunctional code as an answer.
The best answer [which is how I solved my problem] is to use a third party pod/library. I used ImagePicker, and it is quick, fast, FREE, beautiful, and NO memory leaks! [MIT License]
Check it out here:
https://github.com/hyperoslo/ImagePicker
I know there are similar questions available on SO.
The reason why I'm starting a new topic.
I want to take multiple photos and save them into an array for later usage (when user finished taking photos)
I already read the API about showsCameraControl, takePicture() and cameraOverlayView
I managed already to capture multiple photos but I have always to confirm every photo with the "Retake/Use Photo" screen. When tipping on Use Photo, the App saves it into the array.
The same way is already working in the Workflow App from DeskConnect. They have a workflow "Take Photo" where you can choose how many photos you want to take. And they are (in my eyes) using Apple's default controls
Any help is appreciated.
Doesn't matter if it's a solution to avoid "Retake/Use Photo" or to create a custom overly view. For me it is important to keep Apple's default controls so the user feels comfortable with the well known controls.
(I also already took a look on the tutorials Apple provides).
My code so far
var imageArray = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
openCamera()
}
func openCamera() {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.allowsEditing = false
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
imageArray.append(pickedImage)
print(imageArray.count)
}
self.dismiss(animated: true, completion: nil)
openCamera()
}
First off I want to say that I'm by no means an experienced Swift programmer, so if I am missing anything for someone to help me debug my issue - then I'm sorry about that. Just drop a comment and I'll add whatever is necessary to help you help me.
I'm following Apple's guide on how to develop iOS apps with Swift (as seen here).
But for some reason I'm getting this warning:
Instance method 'imagePickerController(picker:didFinishPickingMediaWithInfo:)' nearly matches optional requirement 'imagePickerController(_:didFinishPickingMediaWithInfo:)' of protocol 'UIImagePickerControllerDelegate'
I've attempted to suppress the issue by both using #nonobjc and setting it to private. The problem is that the new image isn't taking the old image's place. Instead it gives me this error in the output:
[Generic] Creating an image format with an unkown type is an error.
This is the code that concerns my tapGesture and the imagePicking:
// MARK: UIImagePickerControllerDelegate
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// The info dictionary contains multiple representations of the image, and this uses the original.
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
// Set photoImageView to display the selected image.
photoImageView.image = selectedImage
// Dismiss the picker.
dismiss(animated: true, completion: nil)
}
// MARK: Actions
#IBAction func selectTapGestureFromPhotoLibrary(_ sender: UITapGestureRecognizer) {
// Hide the keyboard
nameTextField.resignFirstResponder()
// UIImagePickerController allows users to pick media from their photo library!
let imagePickerController = UIImagePickerController()
// Only allow photos to be picked, not taken
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true, completion: nil)
}
If my question isn't clear enough I need help figuring out why the image in my app isn't changing - even though I've followed the guide. I've already noticed some issues that I assume has to do with Xcode having been updated, but not the guide.
It might be worth mentioning that I have already set the info.plist to allow me to access all the necessary security privileges.
I'm running iOS 10.1 and Xcode 8.1
Found a similar issue that together with #Larme's comment helped me solve it:
Similar issue
The code is correct, Please check and sure that you have connect all the necessary outlets.
I am working on a application which needs custome camera UI. SO I used Overlay view in ImagePickerController. User can switch between capture modes.
(UIImagePickerControllerCameraCaptureMode.Video and UIImagePickerControllerCameraCaptureMode.Photo )
My problem is when user swich capture mode from .Photo to .Video startVideoCapture() function is taking time (up to 5 seconds) to initialize the video capturing at firs or second time.
Sometimes it does not save video on specified path..Photo mode is working fine.
At starting I initialized imagePickerController with .Photo mode. And user could switch .Photo to .Video. I thought may be initializing .Video mode would have taken time. For this I initialize the imagePickerController with .Video mode and set a timer to change .video to .Photo mode in 4 seconds. While these 4 seconds user cannot see camera as I have added Loading screen to stop user interaction.
But still problem persist. startVideoCapture() taking time to start recording for very first time. And sometimes it does not save video on specified path after recording.
Here is my code to save video in delegate method.
/**
* Delegate method for media capture and save media
*/
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info:[NSObject : AnyObject]) {
overlayView.bringSubviewToFront(preView)
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
if mediaType.isEqualToString(kUTTypeMovie as NSString as String){
let tempMedia = info[UIImagePickerControllerMediaURL] as! NSURL!
let pathString = tempMedia.relativePath
if pathString != nil {
destinationPathForVideo = "\(UtilityManager.sharedInstance.kcache)/challengeRecordings/\(NSDate()).mp4"
NSFileManager.defaultManager().createDirectoryAtPath(destinationPathForVideo.stringByDeletingLastPathComponent, withIntermediateDirectories: true, attributes: nil, error: nil)
UtilityManager.sharedInstance.copyFileFromSourceTo( pathString, destinationPath: destinationPathForVideo )
// Save Image
imageCaptured = getThumbnailForVideoPath(pathString!)
destinationPathForImage = "\(UtilityManager.sharedInstance.kcache)/challengeRecordings/\(NSDate()).png"
let success = UIImagePNGRepresentation(imageCaptured).writeToFile( destinationPathForImage , atomically: true)
Log(message:"Saved: \(success)")
imageButton!.setImage(imageCaptured, forState: .Normal)
imageView.image = imageCaptured
}
}
This is my initializing code
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .Camera
imagePicker.showsCameraControls = false
self.imagePicker.mediaTypes = [kUTTypeMovie, kUTTypeImage]
self.imagePicker.videoMaximumDuration = 11
self.imagePicker.videoQuality = UIImagePickerControllerQualityType.TypeMedium
self.imagePicker.cameraFlashMode = .Off
self.imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureMode.Video
Somebody please help.
I found a wonderful library with all mediapickercontroller features. You should try it. IQMediaPickerController