10

- (void)videoPickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info

returns different URLs in iOS 13 and the other iOSs.

Any idea why this might be happening?

iOS 13:

file:///private/var/mobile/Containers/Data/PluginKitPlugin/0849234B-837C-43ED-BEDD-DE4F79E7CE96/tmp/trim.B8AB021D-F4B6-4E50-A93C-8B7F7FB40A1C.MOV

< iOS 13:

file:///private/var/mobile/Containers/Data/Application/5AE52A95-6A2F-49A5-8210-D70E022E9A05/tmp/5A8D81B5-FC42-4228-9514-CD998A4E7FA9.MOV

This caused me to have an error since I don’t have permissions to the PluginKitPlugin folder.

In both cases, I’m selecting a video using the imagePicker.

Kunal Shah
  • 1,071
  • 9
  • 24
  • Why are using the url instead of the the actual media? – Mojtaba Hosseini Sep 05 '19 at 05:33
  • @MojtabaHosseini I try to copy/move the file and hence I require the URL. – Kunal Shah Sep 05 '19 at 05:40
  • content of both URLs is different? – Muhammad Shauket Sep 05 '19 at 05:44
  • @ShauketSheikh That has to be the case, since I'm testing it on different devices - one running iOS 12 and the other running iOS 13. But both are simply videos that lie in the gallery of the respective devices and thus they should lie in the folder that have equal permissions. – Kunal Shah Sep 05 '19 at 05:45
  • Then can't you just **save** to new location instead of copy? – Mojtaba Hosseini Sep 05 '19 at 05:52
  • @MojtabaHosseini Not sure what you mean by save? You can think the flow as that a user can upload a video from their gallery using the application, and I perform an intermediate step where I move the video from folder A to folder B for some processing. – Kunal Shah Sep 05 '19 at 06:18
  • I am running into this same issue with iOS13 only. The only url differences are PluginKitPlugin and the trim. on the file name. When I check the NSFileManager to see if there is a file at the path given, the file does not exist. If I figure this out I will update here. – johnrechd Sep 10 '19 at 00:00
  • any update on this? – Jagveer Singh Sep 16 '19 at 18:45
  • My team is still struggling with it. We haven't found a good workaround. I will update here if we get a solution. – johnrechd Sep 16 '19 at 23:50
  • The way we've worked around for this now is we use `copy` instead of `move`. – Kunal Shah Sep 17 '19 at 01:03
  • The answer below from rsidique fixed the issue for my team. The problem was the image picker delegate passed the file's url to another class that then handled copying the file. This has worked fine for several years in our application but with iOS 13, once the image picker delegate goes away, the url immediately becomes invalid. – johnrechd Sep 21 '19 at 23:16
  • Can you explain, how you overcome this issue or share the code snippet. --johnrechd – Ravindra Kishan Sep 30 '19 at 12:00
  • Please see my answer below. I think it has worked for many. – Bumbleparrot Dec 10 '19 at 13:54

5 Answers5

17

I struggled with this for a few nights and finally resolved the issue.

One of the differences in use case here is that I was uploading the video to AWS S3. This happens via the S3 transfer utility in a background thread. With a bunch of experimenting and debugging, Here's what I determined.

The change is that in iOS 13, the mediaURL returned in the info[.mediaURL] parameter from the image picker controller didFinishPickingMediaWithInfo method points to a temporary folder under the "PluginKitsPlugin" directory. It seems like our app doesn't have access to this location for very long.

Example: file:///private/var/mobile/Containers/Data/PluginKitPlugin/0849234B-837C-43ED-BEDD-DE4F79E7CE96/tmp/trim.B8AB021D-F4B6-4E50-A93C-8B7F7FB40A1C.MOV

For some reason (maybe someone else knows) access to that URL is only available temporarily. Some theories here suggest that dismissing the image picker controller will de-allocate the URL thus making it invalid.

With this theory I tried to work around this 2 different ways:

  1. Don't dismiss the image picker until after the upload happens. This did not work. The background process for the S3 transfer utility was still silently dying with the "file not found" error.
  2. Pass a reference to the info dictionary and use it as close to the upload point as possible. I was uploading to AWS S3 so it's possible that the dereferencing of the info dictionary was still happening when S3 went to upload in the background.

What ended up solving the issue was copying the info[.mediaURL] to another place available in my App's temporary folder.

Here's the code I used to copy the info[.mediaURL] to my App's temporary folder.

     This function will copy a video file to a temporary location so that it remains accessbile for further handling such as an upload to S3.
     - Parameter url: This is the url of the media item.
     - Returns: Return a new URL for the local copy of the vidoe file.
     */
    func createTemporaryURLforVideoFile(url: NSURL) -> NSURL {
        /// Create the temporary directory.
        let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
        /// create a temporary file for us to copy the video to.
        let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(url.lastPathComponent ?? "")
        /// Attempt the copy.
        do {
            try FileManager().copyItem(at: url.absoluteURL!, to: temporaryFileURL)
        } catch {
            print("There was an error copying the video file to the temporary location.")
        }

        return temporaryFileURL as NSURL
    }

This code copies to the file to a temp directory like one below where your app has access during its lifecycle: file:///private/var/mobile/Containers/Data/Application/5AE52A95-6A2F-49A5-8210-D70E022E9A05/tmp/5A8D81B5-FC42-4228-9514-CD998A4E7FA9.MOV

You will notice that selecting images (info[.imageURL]) to upload will return a file in the same directory. There were no prior issues uploading images.

With that the S3 transfer utility was able to access the file in a background thread and finish the video upload to S3.

Bumbleparrot
  • 323
  • 1
  • 8
  • 1
    copying very large files even locally from one path to another can block the UI thread. So it is best to do the copy on the background thread as well as the transfer to S3. The only way I was able to fix my similar issue was to keep a private instance variable to point to the info dictionary and retain it while I dismissed the picker and finished copying the local file. It seems to work fine that way. – ucangetit Oct 29 '19 at 03:14
  • this helped, thank you! For anyone newbies like myself wondering how to call this you can do something like: let tempNSURL = createTemporaryURLforVideoFile(url: videoFileURL) - then use the tempNSURL as you want. – TJMac Nov 27 '19 at 22:50
  • Thanks a lot!, You saved my day <3 +1! – Salman Khakwani Dec 04 '19 at 17:08
  • I have been having the same issue uploading to Firebase and this fixed the problem, thank you – Mostafa ElShazly Dec 09 '19 at 06:44
7

The issue may be connected to the lifetime of the url, which is extant with the lifetime of NSDictionary<UIImagePickerControllerInfoKey,id> *)info object. If the object is deallocated, the url is invalidated. So you can keep a reference to the object or copy the media to a more permanent location. I had a similar issue after the update to iOS 13 / Xcode 11.

Note: this answer was modified to conform with the information provided by @mstorsjo, also in this thread: https://stackoverflow.com/a/58099385/3220330

rsidique
  • 79
  • 5
4

To expand on and clarify the earlier answers. You need to keep a reference to the (NSDictionary<UIImagePickerControllerInfoKey,id> *)info object, for as long as you are going to use the url. This dictionary contains a reference to a PHAsset object, which probably controls access to the url - after the asset object is unreferenced and released, the earlier url becomes unreadable.

Other workarounds for the issue, by copying the url to another temporary file within the app's own sandbox, probably work by actually using the source url immediately, while it still is valid and accessible, before the info is released and the url becomes inaccessible again.

mstorsjo
  • 12,983
  • 2
  • 39
  • 62
2

Below is the objective-c version of code . The original fix was given in swift by @Bumbleparrot below :

-(NSURL *)createTemporaryPathforVideoFile:(NSURL *)url{
    NSURL *tempURL =  [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:true];
    NSURL *tempFileURL = [tempURL URLByAppendingPathComponent:url.lastPathComponent];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager copyItemAtURL:url.absoluteURL toURL:tempFileURL error:nil];
    return tempFileURL ;
}
Sateesh Pasala
  • 772
  • 8
  • 15
0

From what I have gathered from experiencing permissions problems in iOS 13 with this path, it is because the picker is a separate app (with its own permissions) which has become more explicit in iOS 13. So the path it gives you is within its own temporary directory rather than your app's.

For us the code was assuming the path given was our temporary directory. I switched to explicitly caching into NSTemporaryDirectory.

ajso
  • 73
  • 7