2

I have an issues in changing the file path at every launch of the app. I have a file("AppConstant.json") in application bundle, and this file I need to copy into application document directory. I am successfully saving "AppConstant.json" file inside the created user folder "MyFolder" on Document directory.

But the problem is when I relaunch the application second time, it's not showing the same path. Also I am using relativepath, but still it not getting.

here is the code // calling the directory

let stringAppConstant = copyFileFromBundleToDocumentDirectory(resourceFile: "AppConstant", resourceExtension: "json")

// saving or get exit file path

func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) -> String 
  {
        var stringURLPath = "Error_URLPath"
        let fileManager = FileManager.default
        let docURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
        let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
        let fileName = "\(resourceFile).\(resourceExtension)"
        guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
            return stringURLPath
        }

       if !fileManager.fileExists(atPath: newDestPath.path) {
            do {
                try fileManager.createDirectory(atPath: newDestPath.path,withIntermediateDirectories: true, attributes: nil)
                print("Created folder successfully in :::", newDestPath.path)
            } catch {
                print("Error in creating folder :::",error.localizedDescription);
            }
        }
        else {
            print("Folder is already exist!")
        }
        if fileManager.fileExists(atPath: fullDestPath.path) {
            print("File is exist in ::: \(fullDestPath.path)")
            stringURLPath = fullDestPath.path
        }
        else {
            do {
                try fileManager.copyItem(atPath:  sourcePath, toPath: fullDestPath.path)
                print("Saved file successfully in :::", fullDestPath.path)
                stringURLPath = fullDestPath.path
            } catch {
                print("Error in creating file ::: \(error.localizedDescription)")
            }
        }
        return stringURLPath
    }

Please help me, where I need to save the path in Sandbox. Is this right way what I implemented.

I am running in device and simulator, both path are different while relaunch this is the path for first time launch: /var/mobile/Containers/Data/Application/81B568A7-0932-4C3E-91EB-9DD62416DFE8/Documents/MyFolder/AppConstant.json

relaunch the application I am getting new path: /var/mobile/Containers/Data/Application/3DAABAC3-0DF5-415B-82A5-72B204311904/Documents/MyFolder/AppConstant.json

NOTE: I create a sample project and I use this same code and it's working. But in existing project it's not working. I am using the same bundle id and profile only for both sample and project. Checked the file added reference, settings, version all are same.

Any idea?

Prasanth
  • 329
  • 2
  • 14
  • Questions to help narrow it down - when you say 'not showing the same path', do you mean fullDestPath? Also, are you running in simulator, or on an actual device? When you 'launch again', are you launching the same way, or differently (for example debugging from Xcode for one launch and not the other)? – Corbell Aug 15 '20 at 08:01
  • Hi @Corbell, I update my question. I launched both device and simulator both also same behaviour. next time I am launching the same way to call the method – Prasanth Aug 15 '20 at 08:07
  • What do you do with the return value of `copyFileFromBundleToDocumentDirectory`? If you store it to access the file later that might be the problem because it contains the absolute path not relative. – Gleb A. Aug 15 '20 at 08:13
  • Hi @GlebA., while launching application(first launch - first time install) I am saving the file in document directory, after that I need to use the same(already saved file in the document directory) for further use. So how can I get "relative path". please guide me – Prasanth Aug 15 '20 at 08:16
  • I guess this statement is wrong `let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)")` which can be replaced with `let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: resourceExtension)` – Sauvik Dolui Aug 15 '20 at 09:19
  • Hi @SauvikDolui, changed again the same. At every launch creating new, not able to find the existing – Prasanth Aug 15 '20 at 09:48
  • @Prasanth can you show the actual code that uses `stringAppConstant` before and after relaunch? (And if you have other places calling `copyFileFromBundleToDocumentDirectory` that'd also be great to see.) – Gleb A. Aug 15 '20 at 11:15
  • Hi @GlebA.Sory for late message, after that I am passing the "stringAppConstant" - file path to cpp internal class and in cpp it write some json data and store it, if it next time launch, I will check the data and parse the json data and redirect based on the respective ViewController in iOS application. Actually the cpp side writing is happend successfully in the file, but the problem is next time if I relaunch the application it getting new path. So it,s looks like as new iOS application. this is what the problem – Prasanth Aug 16 '20 at 05:42
  • @Prasanth did you ever get the answer? – JLively Feb 21 '22 at 21:59

3 Answers3

3

The behavior that the container path changes periodically is normal.

These lines

let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
let fileName = "\(resourceFile).\(resourceExtension)"
guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
    return stringURLPath
}

contain a lot of mistakes

  • URL(string is the wrong API for file paths, it's URL(fileURLWithPath).
  • The second parameter of path(forResource:ofType:) must not have a leading dot.
  • The API absoluteString is wrong as parameter of URL(fileURLWithPath
  • Not a real mistake but don't use NSURL in Swift.

It's highly recommended to use always the URL related API to concatenate paths and get the documents folder from FileManager. Further it's good practice to make the method throw the real error rather than returning a meaningless literal string. And NSSearchPathForDirectoriesInDomains is outdated and should not be used in Swift.

func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) throws -> URL
{
    let sourceURL = Bundle.main.url(forResource: resourceFile, withExtension: resourceExtension)!
    
    let fileManager = FileManager.default
    let destFolderURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("MyFolder")
    let fullDestURL = destFolderURL.appendingPathComponent(resourceFile).appendingPathExtension(resourceExtension)
    
    if !fileManager.fileExists(atPath: destFolderURL.path) {
        try fileManager.createDirectory(at: destFolderURL, withIntermediateDirectories: true, attributes: nil)
        print("Created folder successfully in :::", destFolderURL.path)
        try fileManager.copyItem(at: sourceURL, to: fullDestURL)
        print("Saved file successfully in :::", fullDestURL.path)
    } else {
        print("Folder already exists!")
        if fileManager.fileExists(atPath: fullDestURL.path) {
            print("File exists in ::: \(fullDestURL.path)")
        } else {
            try fileManager.copyItem(at: sourceURL, to: fullDestURL)
            print("Saved file successfully in :::", fullDestURL.path)
        }
    }
    return fullDestURL
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Hi @vadian, now also I am getting same error, every time it creating a new path and file. At the same time in sample project I created, there it's working now. I given same bundle ID, App name and profile also same. Did I want to add any other – Prasanth Aug 16 '20 at 09:42
  • I think some issues in App Sandbox account or setting path - not sure, because the same code is working(provided by you) in new sample test project – Prasanth Aug 16 '20 at 09:45
  • 1
    The container path (the UUID) changes but the contents of the container must not change. Never save the full path, use always the relative path with `.documentDirectory` – vadian Aug 16 '20 at 10:00
  • Hi @vadian, I hope the issues is some other place, because the same code is working in sample project. I really need to find. using relative path also not working. Thanks – Prasanth Aug 16 '20 at 10:45
0

Edit 1:

Hi I created the new project and use the same code I posted in main, and it's working. But in the real project it not working.

Not sure what exactly going on in your project, try to debug it. It's part of development as well. :)

If you are in hurry to fix this issue in this weekend try to use the following code snippet.

// collect data from bundle
let constFileURL = Bundle.main.url(forResource: "AppConst", withExtension: "json")!
let data = try! Data(contentsOf: constFileURL)

// try to write data in document directory
do {
    let constFileURL = try saveFileInDocumentDirectory(filePath: "MyFolder/AppConst.json", data: data)
    // use your `constFileURL`
} catch (let error as FileOperationError) {
    switch error {
    case .fileAlreadyExists(let url):
        let data = try! Data(contentsOf: url)
        print(String(data: data, encoding: .utf8))
    case .IOError(let error):
        print("IO Error \(error)")
    }
} catch {
    print("Unknown Error \(error)")
}

// Helpers
enum FileOperationError: Error {
    case fileAlreadyExists(url: URL)
    case IOError(Error)
}
func saveFileInDocumentDirectory(filePath: String, data: Data) throws -> URL {
    
    // final destination path
    let destURLPath = fullURLPathOf(filePath, relativeTo: .documentDirectory)
    // check for file's existance and throw error if found
    guard FileManager.default.fileExists(atPath: destURLPath.path) == false else {
        throw FileOperationError.fileAlreadyExists(url: destURLPath)
    }
    // Create Intermidiate Folders
    let intermidiateDicPath = destURLPath.deletingLastPathComponent()
    if FileManager.default.fileExists(atPath: intermidiateDicPath.path) == false {
        do {
            try FileManager.default.createDirectory(at: intermidiateDicPath, withIntermediateDirectories: true, attributes: nil)
        } catch {
            throw FileOperationError.IOError(error)
        }
    }
    
    // File Writing
    do {
        try data.write(to: destURLPath, options: .atomic)
    } catch {
        throw FileOperationError.IOError(error)
    }
    return destURLPath
}
func fullURLPathOf(_ relativePath: String, relativeTo dic:FileManager.SearchPathDirectory ) -> URL {
    return FileManager.default.urls(for: dic, in: .userDomainMask).first!.appendingPathComponent(relativePath)
}

Original Answer

Why don't you just return "MyFolder/\(fileName)" on successful file operation? If you need to access the path later you can always do that using FileManager APIs.

let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let constFilePath = docDir.appendingPathComponent("MyFolder/\(fileName)")

// Access const file data
do { 
  let fileData = try Data(contentsOf: constFilePath)

  // Use you data for any further checking

} catch {
  // Error in reading file data
  print("Error in file data access : \(error)")
}

Sauvik Dolui
  • 5,520
  • 3
  • 34
  • 44
  • Hi is anyway to solve this, now also at every launch it creating as new path, I need if already file is there, then I need the exact path, not want to recreate the folder and file again. Any idea – Prasanth Aug 15 '20 at 09:51
  • Hi @Sauvik, I am always getting nil in fileData (let fileData = try? Data(contentsOf: constFilePath)), because of file unavailable in the path – Prasanth Aug 16 '20 at 06:31
  • @Prasanth just check the edited code snippet using `do-try-catch`, can you check the error your are getting? – Sauvik Dolui Aug 16 '20 at 08:36
  • Hi I am getting the file "AppConstant.json" couldn't opend because there is no such file – Prasanth Aug 16 '20 at 08:55
  • I made a mistake, rather than using `.documentDirectory`, I used `.applicationSupportDirectory`. Can you try now? – Sauvik Dolui Aug 16 '20 at 09:01
  • Hi I created the new project and use the same code I posted in main, and it's working. But in the real project it not working. Also I give the same Bundle identifier and profiles. I not understand why – Prasanth Aug 16 '20 at 09:01
  • Try the new answer under `Edit 1` if you want to skip debugging the issue for now. Also do the sanity checking(force unwrap removal, error handling) to make it prod ready is upto you. – Sauvik Dolui Aug 16 '20 at 09:58
  • Hi @Sauvik, very thanks to support. You are right, issues in some other in project really I need to find. Thankyou again. not resolved – Prasanth Aug 16 '20 at 10:20
0

This worked for me:

Since the app sandbox directory changes every time, you need to get the new app directory URL and append the file name, and that's it!!

Example:

let newPath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(oldUrl.lastPathComponent)
let newUrl = URL(fileURLWithPath: newPath)

Where oldUrl.lastPathComponent is your fileName.extension (Ex: myFile.mp3)

PD: In my case, I did not create subfolders. If you created subfolders, then you must append the subfolder and then the fileName.extension

I hope this helps.