Search code examples
iosswiftfile-manager

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?


Solution

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