29

I download the video file from url and save it in document directory with this path:

  let destination: DownloadRequest.DownloadFileDestination = { _, _ in
      let pathComponent = "pack\(self.packID)-\(selectRow + 1).mp4"
      let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
      let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
      let fileURL: URL = folderPath.appendingPathComponent(pathComponent)
      return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
    }

my video is downloaded and plays successfully. but there is a problem, when I rebuild application in Xcode and try to play the last video that I downloaded, video is not shown, and when I download a new video this save and play successfully.

I've seen each video bundle path, they are different.

1 - file:///Users/myMac/Library/Developer/CoreSimulator/Devices/EAC2F4CE-EA09-46C0-B403-1CE9E24B6822/data/Containers/Data/Application/1D2C1F7B-E627-4898-91C1-D0AF8D5E0F1E/Documents/Downloads/pack7-1.mp4

2 - file:///Users/myMac/Library/Developer/CoreSimulator/Devices/EAC2F4CE-EA09-46C0-B403-1CE9E24B6822/data/Containers/Data/Application/F950E9A5-C9F3-4B8C-BCF5-647EEC233CEE/Documents/Downloads/pack7-3.mp4

Now, my question is, when we update the app from the App Store, it means a reinstallation? Does this path change?

how can solve this problem?

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
ava
  • 1,148
  • 5
  • 15
  • 44
  • @ivarun : Not true! iOS 8 onwards absolute url of app changes every time app launches have a look https://stackoverflow.com/questions/26988024/document-or-cache-path-changes-on-every-launch-in-ios-8 – Sandeep Bhandari Dec 18 '17 at 08:05
  • @ivarun i test it in device when run app from XCODE repeatedly without uninstall app thats happen too in device. – ava Dec 18 '17 at 08:15
  • @ava : Did you check the answer I posted ? Didn't that help ? any further issues ? – Sandeep Bhandari Dec 18 '17 at 08:19
  • @SandeepBhandari yes i have did same as u shown in our answer but at time of retrieving the file not found error is coming. – veeresh kumbar Aug 03 '18 at 06:06

4 Answers4

62

iOS 8 onwards, Absolute url to app's sandbox changes every time you relaunch the app. Hence you should never save the absolute url of the video. Save the name of the video and recreate the url every time you relaunch the app.

  let pathComponent = "pack\(self.packID)-\(selectRow + 1).mp4"
  let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
  let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
  let fileURL: URL = folderPath.appendingPathComponent(pathComponent)

Once you have fileURL look for the file and you will find the file downloaded in previous launch.

iOS creates a new Sandbox for app every time user launches the app. Hence absolute URL will very. But iOS will take care of setting up all the folders and contents inside the Sandbox as it was earlier. So though base url of SandBox change, relative url's of all the content will be remained intact.

Hence its advised never to save absolute url to any folder :) Hope it helps

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • someOne can access this folder in document directory and copy this folder content? – ava Dec 18 '17 at 08:42
  • @ava : If that somebody has the access to iPhone then yes :) You can always connect your iPhone to Mac and use third party apps to open the apps sandbox and copy the content. If you want to secure the file against these kinds of access there are multiple solutions – Sandeep Bhandari Dec 18 '17 at 08:44
  • @ava : Solution 1: Save the file in temp folder. None of the contents saved in temp folder are backed by iCloud and no apps can access them as well. But a catch is that content in temp folder are not persisted. When app is killed all the content in temp folder are lost :D Hence the name temp. read :https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html – Sandeep Bhandari Dec 18 '17 at 08:46
  • ohh, i'm using Alamofire download method, i should find solution that this video downloaded don't save as mp4 file. thanks for reply. – ava Dec 18 '17 at 08:51
  • @ava : Wait for a bit more I have two more solutions for file security :) Then take a call – Sandeep Bhandari Dec 18 '17 at 08:51
  • @ava : Solution 2: Enable file protection option in your project setting and add your folder for protection. Use `NSFileProtectionComplete` to enable iOS to encrypt the file to make it unavailable to read and write when the device is locked. In most cases this file protection should be enough. Even though somebody gets the device, unless he is the owner of the device he wont be able to access file read : https://developer.apple.com/documentation/foundation/nsfileprotectiontype?language=objc But if you want to save your app data from the owner of the device as well – Sandeep Bhandari Dec 18 '17 at 08:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161431/discussion-between-ava-and-sandeep-bhandari). – ava Dec 18 '17 at 08:58
  • @ava : In that case use custom encryption and decryption algorithm before saving the data and reading the data from document directory. Single key (private key) encryption would be sufficient in your case :) Else RSA is the best choice (bit over engineered for you as u dont have private and public keys) – Sandeep Bhandari Dec 18 '17 at 09:00
  • can u please add more code, i am stuck. Consider i am trying to download & save an image file to device and want to access it after relaunch. Please help – veeresh kumbar Aug 03 '18 at 06:07
  • @veeresh-kumbar : If you are saving relative path to file being downloaded properly and re creating file path again there can be nothing that will go wrong buddy :) May be u are not saving the file correctly, do one thing after saving the file on your device, go to organizer in Xcode and download the document directory and check if file is actually downloaded or not, if downloaded what its name, same as what u saved or not :) If you are using background session n download task the file will be downloaded temporarily and you have to transfer it to persistent file storage once downloaded – Sandeep Bhandari Aug 03 '18 at 07:47
8

I suggest using a bookmark. Check out the section Locating Files Using Bookmarks in the following article: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html#//apple_ref/doc/uid/TP40010672-CH3

Edit:

Some relevant parts of the linked document, just in case:

Important: Although they are safe to use while your app is running, file reference URLs are not safe to store and reuse between launches of your app because a file’s ID may change if the system is rebooted. If you want to store the location of a file persistently between launches of your app, create a bookmark as described in Locating Files Using Bookmarks.

I've an app that uses a lot of file handling so I've created the following method in an NSURL category to return bookmark data for a given URL.

- (NSData*)defaultBookmark
{
    NSError* error = nil;
    NSData* bookmarkData =  [self bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
                           includingResourceValuesForKeys:nil
                                            relativeToURL:nil
                                                    error:&error];
    if (error != nil)
    {
        NSLog(@"error creating bookmark for url '%@': %@", self, error);
    }
    return bookmarkData;
}

To create a NSURL object from bookmark data use something like:

    NSError* error = nil;
    BOOL isStale = NO;
    NSURL* url = [NSURL URLByResolvingBookmarkData:bookmarkData
                                           options:NSURLBookmarkResolutionWithoutUI
                                     relativeToURL:nil
                               bookmarkDataIsStale:&isStale
                                             error:&error];
    if (error != nil || url == nil)
    {
        NSLog(@"Error restoring url from bookmark: %@", error);
    }
    else
    {
        // use url to load file
        ...
    }
D. Mika
  • 2,577
  • 1
  • 13
  • 29
2

One thing that's unclear about accepted answer is how you actually search for file in Document's directory when you want to fetch it from there. Here we go:

To save file (e.g. image):

func saveImage(with imageName: String) {
    guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {return}
    guard let data = image.jpegData(compressionQuality: 1) else {return}
    let url = documentsDirectory.appendingPathComponent(imageName)
    do {
        try data.write(to: url)
    } catch {
        print("Error saving image")
    }
    
}

To fetch file:

func fetchImage(with imageName: String) -> UIImage? {
    let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
    if let path = paths.first {
        let imageURL = URL(fileURLWithPath: path).appendingPathComponent(imageName)
        let data = try! Data(contentsOf: imageURL)
        if let image = UIImage(data: data) {
            return image
        }
    }
    return nil
}

So once again: the idea is NOT to save the full path to the file, because when you try to get file after reboot from that path that won't work. What you shall do instead is to save the name of the file (imageName in the example above). And then you search for file with that name.

Shalugin
  • 1,092
  • 2
  • 10
  • 15
-1

I had the same problem. This solution worked for me, it is other stack overflow posts put together:

To save the video:

 func saveVideo(videoURL: URL)
  {
    let videoData = NSData(contentsOf: videoURL)

    // *** Get documents directory path *** //
    let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
    // *** Append video file name, save this name somewhere, like core data *** //
    let fileName = UUID().uuidString
   
    let dataPath = paths.appending("/\(fileName).mp4")
    
    if exercise.videoUrls != nil{
        exercise.videoUrls!.append("/\(fileName).mp4")
        try! viewContext.save()
        
    } else {exercise.videoUrls =  ["/\(fileName).mp4"]}

    // *** Write video file data to path *** //
    videoData?.write(toFile: dataPath, atomically: true)
    
}

To load the video:

func loadVideoFromDiskWith(fileName: String) -> URL? {
    
    let documentDirectory = FileManager.SearchPathDirectory.documentDirectory
    
    let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
    let paths = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)
    
    if let dirPath = paths.first {
        let videoUrl = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
        
        return videoUrl
        
    }
    
    return nil
}
T.S
  • 21
  • 1
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 27 '22 at 00:26