Prasanth
Prasanth

Reputation: 329

iOS file path is changing at every launch/rerun the application

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?

Upvotes: 2

Views: 3286

Answers (3)

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.

Upvotes: 0

vadian
vadian

Reputation: 285250

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
}

Upvotes: 4

Sauvik Dolui
Sauvik Dolui

Reputation: 5660

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)")
}

Upvotes: 0

Related Questions