I've just updated Xcode to 8.0 beta 2 and swift 3.0. After updating from swift 2.3 i'm getting a lot of errors.
I have a String-extension that's converting a Range in the "self"-string to a NSRange:
extension String {
func NSRangeFromRange(_ range : Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = String.UTF16View.Index(range.lowerBound, within: utf16view)
let to = String.UTF16View.Index(range.upperBound, within: utf16view)
print("to: \(to) from: \(from)")
print(self.characters.count)
return NSMakeRange(utf16view.startIndex.distance(to: from), from.distance(to: to))
// return NSMakeRange(0, 0) // <-- removes exception
}
}
When NSMakeRange is executed I'm getting a error:
Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'
When I'm printing the to- and from-index's, I'm getting:
to: Index(_offset: 194) from: Index(_offset: 181)
The character-count of the String is 210
, which seems about right.
So, I don't get why it's telling me that the index's are out of bounds, when they are less that the total count.
This line was working perfectly before I updated to swift 3. Back then it was looking like this:
return NSMakeRange(utf16view.startIndex.distanceTo(from), from.distanceTo(to))
The auto-converter didn't update the syntax from swift 2.3 to 3.0, I did that myselves..
Any clues?
In Swift 3, "Collections move their index", see A New Model for Collections and Indices on Swift evolution.
In Swift 2.2, the advancedBy()
and distanceTo()
methods are
called on the index:
let u16 = "12345".utf16
let i1 = u16.startIndex.advancedBy(1)
let i2 = u16.startIndex.advancedBy(3)
let d = i1.distanceTo(i2)
print(d) // 2
This methods still exist in Swift 3 but give unexpected results, at least on character collections:
let u16 = "12345".utf16
let i1 = u16.startIndex.advanced(by: 1)
let i2 = u16.startIndex.advanced(by: 3)
let d = i1.distance(to: i2)
print(d) // -2
The correct way is to use the index()
and distance()
methods
of the collection itself:
let u16 = "12345".utf16
let i1 = u16.index(u16.startIndex, offsetBy: 1)
let i2 = u16.index(u16.startIndex, offsetBy: 3)
let d = u16.distance(from: i1, to: i2)
print(d) // 2
Applied to your problem, this is how you can
convert a Range<String.Index>
to the corresponding NSRange
in Swift 3
(copied from https://stackoverflow.com/a/30404532/1187415):
extension String {
func nsRange(from range: Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = range.lowerBound.samePosition(in: utf16view)
let to = range.upperBound.samePosition(in: utf16view)
return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from),
utf16view.distance(from: from, to: to))
}
}
And for the sake of completeness, this is the opposite conversion
extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self)
else { return nil }
return from ..< to
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With