Search code examples
iosswiftnsdatapoint-clouds

iOS Swift replace or update specific line in a file using file handle?


I am writing a point cloud file and need to keep updating the file header with the total number of points in a file: vertexCount. I don't know when the points will stop coming, so I can not just keep accumulating the values and waiting to write it to file.

The vertexCount value is kept on line 3 of ascii file, which is newline terminated.

I only see examples and functions that append data to the end of the file using write(to: URL, options: .atomic)

How can I use FileHandle to replace a specific line in a file, or overwrite the entire header?

   ply
   format ascii 1.0
   element vertex \(vertexCount)

I see this question about replacing file contents using an array. Due to the file having at least 400 thousand lines, I do not want to separate it into individual lines. I was thinking of separating it on the end_header keyword, and then generating a new header, but am not sure how efficient this is.


Solution

  • Well the issue you will face is that when the numbers of digits increase it will overwrite the characters after it. You will need to use a fixed numbers of digits to be able to write them exactly over it (something like 0000000001). The number of lines doesn't really matter because it will replace any new line character after the last digit.

    extension FixedWidthInteger where Self: CVarArg {
        func strZero(maxLength: Int) -> String {
            String(format: "%0*d", maxLength, self)
        }
    
        func write(toPLYFile atURL: URL) throws {
            let fileHandle = try FileHandle(forUpdating: atURL)
            try fileHandle.seek(toOffset: 36)
            try fileHandle.write(contentsOf: Data(strZero(maxLength: 10).utf8))
            fileHandle.closeFile()
        }
    }
    
    var vertexCount = 1
    let text = """
    ply
    format ascii 1.0
    element vertex \(vertexCount.strZero(maxLength: 10))
    abcdefghijklmnopqrstuvwxyz
    1234567890
    """
    print(text)
    print("=========")
    let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        .appendingPathComponent("file.txt")
    try Data(text.utf8).write(to: fileURL, options: .atomic)
    let fileHandle = try FileHandle(forUpdating: fileURL)
    try fileHandle.seek(toOffset: 36)
    vertexCount = 12345
    try fileHandle.write(contentsOf: Data(vertexCount.strZero(maxLength: 10).utf8))
    fileHandle.closeFile()
    let stringLoaded = try String(contentsOf: fileURL)
    print(stringLoaded)
    

    This will print

    ply
    format ascii 1.0
    element vertex 0000000001
    abcdefghijklmnopqrstuvwxyz
    1234567890
    =========
    ply
    format ascii 1.0
    element vertex 0000012345
    abcdefghijklmnopqrstuvwxyz
    1234567890


    Updated use:

    do {
        // filepath to PLY file that is being updated
        let url = URL(fileURLWithPath: path) 
        
        let totalVertexCount = 12345
        try totalVertexCount.write(toPLYFile: url)
    } catch {
        print("Error writing PLY! \(error)")
        return
    }