I'm trying to crop string on slices and send them to the server. Let’s take an example: I have file with such size of its string - 813753. I have to crop it on slices with max size 10000.All below code will be in file picker function:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file.absoluteURL)
let encodedString = String.init(data: fileData, encoding: .isoLatin1)!
let fileName = file.lastPathComponent
let time = Int64(NSDate().timeIntervalSince1970)
}
}
At the beginning after selection I figure out whether file has result of division >0:
let sliceCounts = encodedString.count%10000 > 0 ? encodedString.count/10000 + 1 : encodedString.count/10000
then in loop I'm trying to get file content slices:
for slice in 0...sliceCounts{
let partSize = slice*10000
let content = encodedString.count%10000 > 0 && partSize >= encodedString.count
? substring(from: partSize - 10000, to: encodedString.count, s:
encodedString) : substring(from: partSize, to: partSize+10000, s: encodedString)
}
I use such method for getting file part:
func substring(from : Int, to : Int, s : String) -> String {
let start = s.index(s.startIndex, offsetBy: from)
let end = s.index(s.startIndex, offsetBy: to)
return String(s[start..<end])
}
And the problem is that I receive such error:
Fatal error: String index is out of bounds: file Swift/StringCharacterView.swift, line 60
It happens when I'm trying to get the last slice. I think the problem is connected with this condition :
encodedString.count%10000 > 0 && partSize >= encodedString.count
and also I think that maybe I have some problems with sending indexes to my method, due to the error. Maybe someone will see where I make a mistake and will help :)
I see a few issues in your for loop, first you loop one iteration to much and secondly for the end of the string you need to consider that the last chunk might be smaller.
for slice in 0..<sliceCounts {
let partSize = slice * maxSize
let content: String
if partSize + maxSize > encodedString.count {
content = String(encodedString.suffix(encodedString.count - partSize))
} else {
content = encodedString.count%maxSize > 0 && partSize >= encodedString.count
? substring(from: partSize - maxSize, to: encodedString.count, s:
encodedString) : substring(from: partSize, to: partSize+maxSize, s: encodedString)
}
}
Note that I used a constant maxSize instead of a hardcoded value in my code
Related
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.
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
}
This is just something I've written really quick, definitely look into making it look and work a bit better - but should be enough to demonstrate the concept. Needs some testing to see if this is actually more efficient than separating line by line.
What it's essentially doing is it's reading the file byte by byte into an array until it finds the beginning of the third line. Then, it copies our new string into the buffer. After that, it's looking for the beginning of the fourth line and copying the rest of the file into the buffer.
I'm also calculating the total byte size so that I can trim the buffer at the end.
var finalFileByteLength = 0;
if let d = NSData(contentsOfFile: filePath) {
let newLine = "element vertex \(vertexCount)\n".data(using: .ascii)! as NSData
var buffer = [UInt8](repeating: 0, count: d.length + newLine.length)
var bytePosition = 0
var lineCount = 0
while(true) {
//Read one Byte
d.getBytes(&buffer+bytePosition, range: NSMakeRange(bytePosition, 1))
//If it's a new line character
if(buffer[bytePosition] == 10) {
lineCount += 1
//If it found the end of the second line, copy our new line
if lineCount == 2 {
newLine.getBytes(&buffer+(bytePosition+1), length: newLine.length)
bytePosition += 1
break
}
}
bytePosition += 1
finalFileByteLength+=1
}
var oldLine3Length = 0
finalFileByteLength+=newLine.length
//Find the start of the fourth line in the initial file
while(true) {
//Read one Byte
var char = UInt8()
d.getBytes(&char, range: NSMakeRange(bytePosition, 1))
//If it's a new line character
if(char == 10) {
//If it found the end of the third line, break so we have the start of the fourth line
bytePosition += 1
oldLine3Length += 1
break
}
bytePosition += 1
oldLine3Length += 1
}
//Header is now modified, copy the rest of the file
d.getBytes(&buffer+(bytePosition+newLine.length-oldLine3Length), range: NSMakeRange(bytePosition, d.length - bytePosition))
finalFileByteLength+=d.length - bytePosition + 1
let finalFileData = NSData(bytes: &buffer, length: finalFileByteLength)
//Print the result - this is probably where you'll write the entire String to a file
print(String(data: finalFileData as Data, encoding: .ascii))
}
EDIT: Managed to reduce to this:
if let d = NSData(contentsOfFile: filePath) {
let newLine = "element vertex \(vertexCount)".data(using: .ascii)! as NSData
var newLineBuffer = [UInt8](repeating: 0, count: newLine.length)
newLine.getBytes(&newLineBuffer, length: newLine.length)
var buffer = [UInt8](repeating: 0, count: d.length)
d.getBytes(&buffer, length: d.length)
var thirdIndex = buffer.firstIndex(of: 10)
thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
var fourthIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
buffer.removeSubrange(thirdIndex!+1..<fourthIndex!)
buffer.insert(contentsOf: newLineBuffer, at: thirdIndex!+1)
let finalFileData = NSData(bytes: buffer, length: buffer.count) as Data
print(String(data:finalFileData, encoding: .ascii))
}
How can I get the only the PCM data from AVAudioRecorder file?
these are the settings I use to record the file:
let settings : [String : Any] = [
AVFormatIDKey: Int(kAudioFormatLinearPCM),
AVSampleRateKey: Int(stethoscopeSampleRateDefault),
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.medium.rawValue,
]
the outcome of this is strange wav file with strange header.
How can I extract only the PCM data out of it?
The actual sound data in a wav file is in the "data" subchunk of that file - this format description might help you visualize the structure you'll have to navigate. But maybe what's tripping you up is that Apple includes an extra subchunk called "fllr" which precedes the sound data, so you have to seek past that too. Fortunately every subchunk is given an id and size, so finding the data subchunk is still relatively straightforward.
Open the file using FileHandle
Seek to byte 12, which gets you past the header and puts you at the beginning of the first subchunk (should be fmt).
Read 4 bytes and convert to a string, then read 4 more bytes and convert to an integer. The string is the subchunk name, and the integer is the size of that subchunk. If the string is not "data" then seek forward "size" number of bytes and repeat step 3.
Read the rest of the file - this is your PCM data.
With Jamie's guidance I managed to solve this. Here is my code:
func extractSubchunks(data:Data) -> RiffFile?{
var data = data
var chunks = [SubChunk]()
let position = data.subdata(in: 8..<12)
let filelength = Int(data.subdata(in: 4..<8).uint32)
let wave = String(bytes: position, encoding: .utf8) ?? "NoName"
guard wave == "WAVE" else {
print("File is \(wave) not WAVE")
return nil
}
data.removeSubrange(0..<12)
print("Found chunks")
while data.count != 0{
let position = data.subdata(in: 0..<4)
let length = Int(data.subdata(in: 4..<8).uint32)
guard let current = String(bytes: position, encoding: .utf8) else{
return nil
}
data.removeSubrange(0..<8)
let chunkData = data.subdata(in: 0..<length)
data.removeSubrange(0..<length)
let subchunk = SubChunk(name: current, size: length, data: chunkData)
chunks.append(subchunk)
print(subchunk.debugDescription)
}
let riff = RiffFile(size: filelength, subChunks: chunks)
return riff
}
Here's the definition for RiffFile and SubChunk structs:
struct RiffFile {
var size : Int
var subChunks : [SubChunk]
}
struct SubChunk {
var debugDescription: String {
return "name : \(name) size : \(size) dataAssignedsize : \(data.count)"
}
var name : String
var size : Int
var data : Data
}
Please help me. I updated to Xcode 10 where is a Swift 4.2. And now format %ld (also others) doesn't work anymore. Who knows what should I use instead that?
let name = String(format: "Pic_%ld", value)
Part of code:
if value != 0 && value != 6 && value != 9 {
let name = String(format: "Pic_%ld", value)
print("Pic_%ld")
print(value)
let tileNode = SKSpriteNode(imageNamed: name)
print(name)
tileNode.size = CGSize(width: Width, height: Height)
var point = pointFor(column: column, row: row)
point.x -= Width/2
point.y -= Height/2
tileNode.position = point
tilesLayer.addChild(tileNode)
}
And see that
Pic_%ld
-4
2018-09-23 19:46:52.799361+0800 [20784:2265405] SKTexture: Error loading image resource: "Pic_-4"
Pic_-4
You can format any string by following example .
string = " \(aNumber) and \(aString)" // wher aNumber is a Int , aString is a string Variable
in you case it should be:
name = "Pic_\(value)"
So your code works fine.
// This command prints "Pic_%ld" because there is no formatting, it's just a string
print("Pic_%ld")
// This command prints "-4" because it's the value of the variable
print(value)
// And these commands prints "Pic_-4". Because your value is -4
let name = String(format: "Pic_%ld", value)
print(name)
And also you get this error
SKTexture: Error loading image resource: "Pic_-4"
for an obvious reason, there is no such image in your resources. but anyway, String formatting works fine in your case.
I'm looking for a fast, optimized way to trim log files on iOS. I want to specify that my log files have a maximum number of lines (e.g., 10,000). Appending new lines to the end of a text file seems relatively simple. However, I haven't yet found a fast way to trim lines at the beginning of the file. Here's the (slow) code I came up with.
guard let fileURL = self.fileURL else {
return
}
guard let path = fileURL.path else {
return
}
guard let fileHandle = NSFileHandle(forUpdatingAtPath: path) else {
return
}
fileHandle.seekToEndOfFile()
fileHandle.writeData(message.dataUsingEncoding(NSUTF8StringEncoding)!)
fileHandle.writeData("\n".dataUsingEncoding(NSUTF8StringEncoding)!)
currentLineCount += 1
// TODO: This could probably use some major optimization
if currentLineCount >= maxLineCount {
if let fileString = try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding) {
var lines = fileString.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
lines.removeFirst()
let newData = lines.joinWithSeparator("\n")
fileHandle.seekToFileOffset(0)
fileHandle.writeData(newData.dataUsingEncoding(NSUTF8StringEncoding)!)
}
}
fileHandle.closeFile()
There are two aspects of your question. First, your code removes a single
line from the log file. Therefore, once the limit is reached, every new
log message causes the entire file to be read, shortened, and be re-written.
It would be more effective to use a "high-water mark" and a "low-water mark". For example, if you want the last 10.000 lines to be preserved,
let the log file grow until it has 15.000 lines, and only then truncate
it to 10.000 lines. This reduces the number of "trim actions"
considerably.
The second part is about the truncating itself. Your code loads the
file into an NSString, which requires the conversion of UTF-8 data
to Unicode characters
(and fails if there is a single invalid byte in the log file).
Then the string is split into an array, one array element removed,
the array concatenated to a string again, and then written back
to the file, which converts the Unicode characters to UTF-8.
I haven't done performance tests, but I can imagine that it could be
faster to operate on binary data only, without the conversions to
NSString, Array and back. Here is a possible implementation
which removes a given number of lines from the start of a file:
func removeLinesFromFile(fileURL: NSURL, numLines: Int) {
do {
let data = try NSData(contentsOfURL: fileURL, options: .DataReadingMappedIfSafe)
let nl = "\n".dataUsingEncoding(NSUTF8StringEncoding)!
var lineNo = 0
var pos = 0
while lineNo < numLines {
// Find next newline character:
let range = data.rangeOfData(nl, options: [], range: NSMakeRange(pos, data.length - pos))
if range.location == NSNotFound {
return // File has less than `numLines` lines.
}
lineNo++
pos = range.location + range.length
}
// Now `pos` is the position where line number `numLines` begins.
let trimmedData = data.subdataWithRange(NSMakeRange(pos, data.length - pos))
trimmedData.writeToURL(fileURL, atomically: true)
} catch let error as NSError {
print(error.localizedDescription)
}
}
I have updated Martin R answer to Swift 3, and I also changed it so that we can pass the number of lines to keep instead of the number of lines to remove:
func removeLinesFromFile(fileURL: URL, linesToKeep numLines: Int) {
do {
let data = try Data(contentsOf: fileURL, options: .dataReadingMapped)
let nl = "\n".data(using: String.Encoding.utf8)!
var lineNo = 0
var pos = data.count-1
while lineNo <= numLines {
// Find next newline character:
guard let range = data.range(of: nl, options: [ .backwards ], in: 0..<pos) else {
return // File has less than `numLines` lines.
}
lineNo += 1
pos = range.lowerBound
}
let trimmedData = data.subdata(in: pos..<data.count)
try trimmedData.write(to: fileURL)
} catch let error as NSError {
print(error.localizedDescription)
}
}
Instead of writing the new line to the log, and processing the appended content afterwards, do both write and trimming in one step:
let fileString = (try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding)) as NSString? ?? ""
var lines = fileString.characters.split("\n").map{String($0)}
lines.append(message)
// this also more generic as it will remove any number of extra lines
lines.removeFirst(max(currentLineCount - maxLineCount), 0))
let newLogContents = lines.joinWithSeparator("\n")
(newLogContents as NSString).writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)
I previously developed an android app that served as a reference guide to users. It used a sqlite database to store the information. The database stores UTF-8 text without formatting (i.e. bold or underlined)
To highlight what sections of text required formatting I enclosed them using delimiter tokens specifically $$ as this does not appear in the database as information. Before displaying the text to the user I wrote a method to find these delimiters and add formatting to the text contained within them and delete the delimiters. so $$foo$$ became foo.
My java code for this is as follows:
private static CharSequence boldUnderlineText(CharSequence text, String token) {
int tokenLen = token.length();
int start = text.toString().indexOf(token) + tokenLen;
int end = text.toString().indexOf(token, start);
while (start > -1 && end > -1)
{
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
//add the formatting required
spannableStringBuilder.setSpan(new UnderlineSpan(), start, end, 0);
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, 0);
// Delete the tokens before and after the span
spannableStringBuilder.delete(end, end + tokenLen);
spannableStringBuilder.delete(start - tokenLen, start);
text = spannableStringBuilder;
start = text.toString().indexOf(token, end - tokenLen - tokenLen) + tokenLen;
end = text.toString().indexOf(token, start);
}
return text;
}
I have recreated my app in Swift for iOS and it is complete apart from showing the correct formatting. It appears that Swift treats strings differently from other languages.
So far I have tried using both NSString and String types for my original unformatted paragraph and get manage to get the range, start and end index of the first delimiter:
func applyFormatting2(noFormatString: NSString, delimiter: String){
let paragraphLength: Int = noFormatString.length //length of paragraph
let tokenLength: Int = delimiter.characters.count //length of token
let rangeOfToken = noFormatString.rangeOfString(formatToken) //range of the first delimiter
let startOfToken = rangeOfToken.toRange()?.startIndex //start index of first delimiter
let endOfToken = rangeOfToken.toRange()?.endIndex //end index of first delimiter
var startOfFormatting = endOfToken //where to start the edit (end index of first delimiter)
}
OR
func applyFormatting(noFormatString: String, token: String){
let paragraphLength: Int = noFormatString.characters.count
let tokenLength: Int = token.characters.count //length of the $$ Token (2)
let rangeOfToken = noFormatString.rangeOfString(formatToken) //The range of the first instance of $$ in the no format string
let startOfToken = rangeOfToken?.startIndex //the starting index of the found range for the found instance of $$
let endOfToken = rangeOfToken?.endIndex //the starting index of the found range for the found instance of $$
var startOfFormatting = endOfToken
}
I appreciate this code is verbose and has pointless variables but it helps me think though my code when I'm working out a problem.
I am currently struggling to workout how to find the second/closing delimiter. I want to search through the string from a specific index as I did in Java using the line
int end = text.toString().indexOf(token, start);
however I cannot work out how to do this using ranges.
Can anyone help me out with either how to correctly identify where the closing delimiter is or how to complete the code block to format all the required text?
Thanks
Aldo
How about using NSRegularExpression?
public extension NSMutableAttributedString {
func addAttributes(attrs: [String : AnyObject], delimiter: String) throws {
let escaped = NSRegularExpression.escapedPatternForString(delimiter)
let regex = try NSRegularExpression(pattern:"\(escaped)(.*?)\(escaped)", options: [])
var offset = 0
regex.enumerateMatchesInString(string, options: [], range: NSRange(location: 0, length: string.characters.count)) { (result, flags, stop) -> Void in
guard let result = result else {
return
}
let range = NSRange(location: result.range.location + offset, length: result.range.length)
self.addAttributes(attrs, range: range)
let replacement = regex.replacementStringForResult(result, inString: self.string, offset: offset, template: "$1")
self.replaceCharactersInRange(range, withString: replacement)
offset -= (2 * delimiter.characters.count)
}
}
}
Here is how you call it.
let string = NSMutableAttributedString(string:"Here is some $$bold$$ text that should be $$emphasized$$")
let attributes = [NSFontAttributeName: UIFont.boldSystemFontOfSize(15)]
try! string.addAttributes(attributes, delimiter: "$$")
The iOS way of doing this is with NS[Mutable]AttributedStrings. You set dictionaries of attributes on text ranges. These attributes include font weights, sizes, colors, line spacing, etc.