Reputation: 3231
I try to get the distance between a string's startIndex and another index, but get the following error in the first loop iteration. The code actually works with most string, but with some it crashes.
fatal error: cannot increment endIndex
let content = NSMutableAttributedString(string: rawContent, attributes: attrs)
var startIndex = content.string.characters.startIndex
while true {
let searchRange = startIndex..<rawContent.characters.endIndex
if let range = rawContent.rangeOfString("\n", range: searchRange) {
let index = rawContent.characters.startIndex.distanceTo(range.startIndex)
startIndex = range.startIndex.advancedBy(1)
rawContent.replaceRange(range, with: "*")
content.addAttribute(
NSForegroundColorAttributeName,
value: UIColor.redColor(),
range: NSMakeRange(index, 1))
}
else {
break
}
}
content.replaceCharactersInRange(NSMakeRange(0, content.length), withString: rawContent)
content
is NSMutableAttributedString
and when the app crashes the variables have the following values:
range.startIndex: 164
content.string.characters.startIndex: 0
content.string.characters.endIndex: 437,
content.string.characters.count: 435
I don't understand why the error message says about increasing endIndex when I'm trying to calculate the distance from the startIndex and anotherIndex
is less than the string length.
Upvotes: 0
Views: 731
Reputation: 2758
The cause for the error is that you are mixing Range<String.Index>
and NSRange
APIs. The first is counting in Character
s and the second in UTF–16 code units. If you start with:
import Cocoa
let content = NSMutableAttributedString(string: "♥️♥️\n")
... then your code enters an infinite loop (this refers to @Tapani's original question and I haven't checked if this is still the case after his changes; the central problem remains the same though)! This is because:
NSString(string: "♥️♥️\n").length // 5
"♥️♥️\n".characters.count // 3
... so that you end up replacing (part of) the second heart with a space, leaving the new line in place, which in turn keeps you in the loop.
One way to avoid these problems is to do something along the lines of:
let content = NSMutableAttributedString(string: "♥️♥️\n")
let newLinesPattern = try! NSRegularExpression(pattern: "\\n", options: [])
let length = (content.string as NSString).length
let fullRange = NSMakeRange(0, length)
let matches = newLinesPattern.matchesInString(content.string, options: [], range: fullRange)
for match in matches.reverse() {
content.replaceCharactersInRange(match.range, withAttributedString: NSAttributedString(string: " "))
}
content.string // "♥️♥️ "
If you copy and paste this into a playground and study the code (e.g. Alt-Click on method names to popup their API), you'll see that this code works exclusively with NSRange
metrics. It is also much safer as we are looping through the matched ranges in reverse so you can replace them with substrings of different length. Moreover, the use of NSRegularExpression
makes this a more general solution (plus you can store the patterns and reuse them elsewhere). Finally, replacing with NSAttributedString
, in addition to replaceCharactersInRange
being NSRange
based, also gives you a greater control in the sense that you can either keep the existing attributes (get them from the range you are replacing) or add others...
Upvotes: 2
Reputation: 6211
Your code worked fine for me, but you should be error checking. And since you are searching the entire content.string
anyway why add the complexity of setting the search range?
Something like this would be simpler:
if let anotherIndex = content.string.rangeOfString("\n")
{
let index = content.string.startIndex.distanceTo(anotherIndex.startIndex)
}
Upvotes: 0