How can [UIImagePickerController.InfoKey.phAsset] as? PHAsset be nil - ios

I sent a user a TestFlight version of my app. She has a video recorded that she made using her iPhone. When the imagePicker was presented and she selected a video it came back as nil. I have an error message with a number 120 that appears in an alert that tells me where the error occurred.
It can only be the asset [UIImagePickerController.InfoKey.phAsset] as? PHAsset is nil. I don't see how that's possible because it's a video that she made using her phone. The odd thing is when she selects a photo everything works fine and when I select videos using iOS 14 and iOS 13 everything works fine.
She's on iOS 15.1, and I'm wondering is that the issue? She's a fellow iOS dev and she said iOS 15 has been causing issues.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset
// ...
if let style = asset?.playbackStyle {
// ...
// if it entered here then the below alert would have never appeared
} else {
let errorMessage = "Error: 120"
let alert = UIAlertController(title: "Unknown Error", message: errorMessage, preferredStyle: .alert)
// ...
}
imagePicker?.dismiss(animated: true, completion: nil)
}
FYI afterwards I just tested on iOS 15.1 with some videos and it worked fine.
I tried the PHPickerController but it's very buggy so I'd rather stick with the ImagePicker for now.

I'm not exactly sure how the asset can be nil but according to this answer by #Sh_Kahn I need to handle all possibilities:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
let style = asset.playbackStyle
// ...
} else if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
// ...
} else if let imageUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL {
// ...
} else {
let errorMessage = "Error: 120"
let alert = UIAlertController(title: "Unknown Error", message: errorMessage, preferredStyle: .alert)
// ...
}
imagePicker?.dismiss(animated: true, completion: nil)
}

Related

Attempting to move file in UIImagePickerController didFinishPickingMediaWithInfo callback fails with "you don't have permission" error

Here is my code for displaying an image picker:
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.allowsEditing = false
pickerController.mediaTypes = [kUTTypeMovie as String]
pickerController.sourceType = .photoLibrary
In the delegate I have:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let videoURL = info[.mediaURL] as? URL
else { return }
do {
let newFileLocation = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".mov")
FileManager.default.moveItem(at: videoURL, to: newFileLocation)
} catch {
// “trim.92501481-FA5B-490C-8F55-575DE076C8A1.MOV” couldn’t be moved because you don’t have permission to access “tmp”
}
}
I went back and tried this on iOS 13.5 and it works fine, but in iOS 13.7 I am getting this strange error.
Ah simple fix, change:
FileManager.default.moveItem(at: videoURL, to: newFileLocation)
to
FileManager.default.copyItem(at: videoURL, to: newFileLocation)
I just discovered the same problem and solution. The funny thing is that if you're doing this for the camera, moving the file works. But if you're doing this for the Camera Roll, it doesn't. Worse of course is that Apple changed this behavior without documenting it anywhere. I too have been using "move" for years for obvious efficiency reasons.
Also, the error says that the app doesn't have permission for the destination folder, when in fact the permission is lacking for the source folder. So no help from iOS here.

Trimmed swift video only saving untrimmed video Xcode 12 Swift 5

Per this tutorial I have set up a simple "record" UIButton, where I am taken to the camera, can record video only, and after taking the video, am taken to the video editor screen where I can trim. Once trimmed, I hit save and the video successfully saves to my camera roll. However, it is only saving the untrimmed video. (update below, with the UIVideoEditorController, I now get 2 unedited videos upon saving).
Code as follows:
VideoHelper.swift
import MobileCoreServices
import UIKit
import AVFoundation
enum VideoHelper {
static func startMediaBrowser(
delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
sourceType: UIImagePickerController.SourceType
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType)
else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType //checks to see if a movie exists on the camera roll, the camera itself, and the photo library
mediaUI.mediaTypes = [kUTTypeMovie as String] //allows only movies to be selected
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
}
RecordVideoViewController.swift
import UIKit
import MobileCoreServices //contain predefined constants such as kUTTypeMovie which lets you select only video
import Photos
import AVFoundation
class RecordVideoViewController: UIViewController {
#IBAction func record(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera) //opens the image picker and chooses the camera itself
}
}
//MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
//1 gives you the URL pointing to the video
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
//2 verify the app can save the file to the device's photo album
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
else { return }
//launches video editor
guard UIVideoEditorController.canEditVideo(atPath: url.path) else { return }
let editor = UIVideoEditorController()
editor.videoPath = url.path
editor.videoMaximumDuration = 10.0
editor.videoQuality = .typeIFrame1280x720
present(editor, animated: true, completion: nil)
//3 if it can, save it
UISaveVideoAtPathToSavedPhotosAlbum(
url.path,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
//Displays an alert announcing whether the video file was saved or not, based on the error status
#objc func video(
_ videoPath: String,
didFinishSavingWithError error: Error?,
contextInfo info: AnyObject
) {
let title = (error == nil) ? "Success" : "Error"
let message = (error == nil) ? "Video was saved" : "Video failed to save"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
present(alert, animated: true, completion: nil)
}
}
//MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
//MARK: - UIVideoEditorControllerDelegate
extension RecordVideoViewController: UIVideoEditorControllerDelegate {
}
I must be close as I have a successful video, it's just I only want the trimmed version.
Update to RecordVideoViewController.swift
I have now added the UIVideoEditorDelegate, and instantialized the delegate, as well as set the delegate and implemented the delegate methods. It now results in two saved videos, of which none are the edited one.
import UIKit
import MobileCoreServices //contain predefined constants such as kUTTypeMovie which lets you select only video
import Photos
import AVFoundation
class RecordVideoViewController: UIViewController {
#IBAction func record(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera) //opens the image picker and chooses the camera itself
}
}
//MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
//1 gives you the URL pointing to the video
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
//2 verify the app can save the file to the device's photo album
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path),
//****2.5? Totally of the rails fromt he tutorial on this one:*****
UIVideoEditorController.canEditVideo(atPath:url.path)
else { return }
//Must instantiate the UIVideoEditorController with "let editor = UIVideoEditorController()" and set the instance's delegate via self as well as show the file path
let editor = UIVideoEditorController()
editor.delegate = self
editor.videoPath = url.path
//presents the UIVideoEditorController
self.present(editor, animated: true)
print(editor.modalPresentationStyle.rawValue)
}
// The UIVideoController's interface on the phone shows cancel and save buttons, which "do not" dismiss the presented view. Must do that in the implementation of the delegate methods, of which there are 3. And all 3 methods must be told to dismiss the presented view.
// Delegate Method #1 (didSaveEditedVideoToPath): Called when the system has finished saving an edited movie. At this point, the the trimmed video has already been saved to a file in app's temporary directory
func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath path: String) {
self.dismiss(animated:true)
}
//Delegate Method #2 (videoEditorControllerDidCancel): Called when the user has cancelled a movie editing operation
func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) {
self.dismiss(animated:true)
}
//Delegate Method #3 (didFailWithError): Called when the video editor is unable to load or save a movie. Important as things "can" fail at this point. MMhmmm
func videoEditorController(_ editor: UIVideoEditorController, didFailWithError error: Error) {
self.dismiss(animated:true)
//***back on the rails with the tutorial, hope this works***
//3 if it can, save it
UISaveVideoAtPathToSavedPhotosAlbum(
editor.videoPath,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
//Displays an alert announcing whether the video file was saved or not, based on the error status
#objc func video(
_ videoPath: String,
didFinishSavingWithError error: Error?,
contextInfo info: AnyObject
) {
let title = (error == nil) ? "Success" : "Error"
let message = (error == nil) ? "Video was saved" : "Video failed to save"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
present(alert, animated: true, completion: nil)
}
}
//MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
//MARK: - UIVideoEditorControllerDelegate
extension RecordVideoViewController: UIVideoEditorControllerDelegate {
}
Code in iOS does not magically "wait" for something to happen. So, when you say this:
let editor = UIVideoEditorController()
// ...
present(editor, animated: true, completion: nil)
UISaveVideoAtPathToSavedPhotosAlbum(
...you present the editor, but instead of waiting for the user to see the editor and do the trim, you immediately just go on to save the video as you originally have it.
So how do you wait? Well, look at the UIVideoEditorController docs. It has a delegate. You must set that delegate (you cannot use a video editor controller without one), and you must implement the delegate method in your delegate. As a result that method will be called — after the user edits! And when it is called it is handed the edited video! So that is where you proceed to save.

Vision Text Recogniser giving nil result

I hope you are doing well, I am stuck with an strange problem. I have implemented Vision Text Recogniser and after successfully extract the image and assign that image to visionImage but I am getting error = nil and also the result = nil. There is nil coming in result block i have followed the google documentation link https://firebase.google.com/docs/ml-kit/ios/recognize-text . but the result is nil.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
imgView.image = pickedImage
// -----
let visionImage = VisionImage(image: pickedImage)
textRecognizer.process(visionImage) { (result, error) in
guard error == nil, let result = result else {
// ...
return
}
print("THE RESULT", result?.text)
// -----
}
dismiss(animated: true, completion: nil)
}
Please help me with that or specify why reason of that nil coming in result block. so that I can fix it.
A big Thank you in advance, waiting for your response.
Regards,
Azeem Usmani

Issue setting default profile pic in Swift (iOS)

I am new to Swift/iOS development, so this might be a stupid question, but I can't seem to find how to do this correctly.
I am following a youtube guide on how to programmatically code (no storyboard) a login screen in Swift and register a user into a Firebase database. The basic outline of the design are as follows
Once the user clicks the icon of the cat with the crown, the image picker comes up and they can select a profile picture and then register like normal. If the user does not select an image from the picker, the cat with the crown icon gets loaded as the profile picture by default.
What I have been trying to do is make it so a different picture (not the cat, and not shown to the user on the login page) named "nedstark" is set as the default profile picture that is stored in the database.
The program uses LoginController.swift and LoginController + handlers.swift files to make this happen. I tried to add a conditional to the picker where if nothing is selected, the default profile pic is set to a specific default user pic (different than the cat), but it doesn't work.
This is the code that stores the photo and other User info into the Database
let imageName = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).jpg")
if let profileImage = self.profileImageView.image, let uploadData = UIImageJPEGRepresentation(profileImage, 0.1) {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if let error = error {
print(error)
return
}
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let values = ["name": name, "email": email, "profileImageUrl": profileImageUrl]
self.registerUserIntoDatabaseWithUID(uid, values: values as [String : AnyObject])
}
})
}
This is the code where I am trying to set the default profile pic
func handleSelectProfileImageView() {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
else { //Testing if default profile pic set
selectedImageFromPicker = UIImage(named: "nedstark")
}
if let selectedImage = selectedImageFromPicker {
profileImageView.image = selectedImage
}
else { //Testing if default profile pic set
profileImageView.image = UIImage(named: "nedstark")
}
dismiss(animated: true, completion: nil)
}
I
I know that this is a total noob question, but any help, tips, or pointers is greatly appreciated. Thanks!
You need to handle delegate method of cancel event. UIImagepickercontrolledelegate has another method
imagePickerControllerDidCancel()
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
imageView.image = UIImage(named: "nedstark")
dismiss(animated: true, completion: nil)
}
When you click on cancel button in pickerviewcontroller then didfinishPickingMedia function won't call. That's way your default image is not set when you tap on cancel button.

Where are the photos taken with my app stored?

Besides browsing the Photolibrary I've enabled the Camera to add a photo to my
#IBAction func startCameraButtonTapped(sender: AnyObject) {
let startCameraController = UIImagePickerController()
startCameraController.delegate = self
startCameraController.sourceType = UIImagePickerControllerSourceType.Camera
startCameraController.allowsEditing = true
self.presentViewController(startCameraController, animated: true, completion: nil)
//invoiceImageHolder.image!.scaleAndRotateImage2(280)
//self.invoiceImageHolder.transform = CGAffineTransformMakeRotation((180.0 * CGFloat(M_PI)) / 180.0)
}
Every user input can be deleted, but now I'd like to know where the corresponding photo is. When I browse the photo library I can't see the photos taken with my app? Where are those photos stored?
[edit]
This is my saveImage process
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
saveImage(selectedImage, path: fileInDocumentsDirectory("\(uniqueImageName)"))
// set the unique image reference in textfield
// Set photoImageView to display the selected image
self.invoiceImageHolder.image = selectedImage
// Dismiss the picker
dismissViewControllerAnimated(true, completion: nil)
}
func saveImage(image: UIImage, path: String) -> Bool{
let pngImageData = UIImagePNGRepresentation(image)
// let jpgImageData = UIImageJPEGRepresentation(image, 1.0)
let result = pngImageData?.writeToFile(path, atomically: true)
//print("Save result \(result)")
return result!
}
EDIT hmm I think I see where the photos are stored, but now how do I browse to them for deletions?
I think it's here:
/var/mobile/Containers/Data/Application/CBFE9E9D-A657-4011-8B9F-EF0C9BB2C603/Documents/
BUT everytime i restart the app the part between Application and /Documents is different??
Still not a working solution, but for those newbies as me this is a valuable readup: http://www.techotopia.com/index.php/Working_with_Directories_in_Swift_on_iOS_8
[EDIT 2]
with this code i am able to see all whiles stored with my app. I was a bit surprise to see all the files
// Files stored in my app filemanager
let filemgr = NSFileManager.defaultManager()
do {
print("Start filemanager")
let filelist = try filemgr.contentsOfDirectoryAtPath(documentsDirectory())
for filename in filelist {
print(filename)
}
} catch {
print("No directory path \(error)")
}
// end filemanager
UIImagePickerController() doesn't automatically save your photos into the library. See its UIImagePickerControllerDelegate protocol and you'll see that it has a method returning your UIImage after making a photo. Then you can save it:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
you save them - not in the photo library - but as 'simply' files inside your app's documents folder. There is no standard UI for deletion then.
You need to write your own ViewController to show & handle deletions then.

Resources