11

I'm attempting to determine the indexes of occurrences of a given string in a String, then generate an NSRange using those indexes in order to add attributes to an NSMutableAttributedString. The problem is rangeOfString returns Range<Index> but addAttributes:range: expects an NSRange. My attempts to create an NSRange from the start and end indexes of the Range have failed, because String.CharacterView.Index is not an Int, thus it will not compile.

How can one use Range<Index> values to create an NSRange?

var originalString = "Hello {world} and those who inhabit it."

let firstBraceIndex = originalString.rangeOfString("{") //Range<Index>
let firstClosingBraceIndex = originalString.rangeOfString("}")

let range = NSMakeRange(firstBraceIndex.startIndex, firstClosingBraceIndex.endIndex)
//compile time error: cannot convert value of type Index to expected argument type Int

let attributedString = NSMutableAttributedString(string: originalString)
attributedString.addAttributes([NSFontAttributeName: boldFont], range: range)
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • I don't see you calling `rangeOfString` anywhere. Your code makes no sense. What the heck is `originalString("{")`???? This cannot possibly be your real code; it is nonsense. – matt Oct 04 '15 at 17:59
  • @matt Missed part of it on 2nd and 3rd lines, updated – Jordan H Oct 04 '15 at 18:13
  • My answer remains the same. Where you now have `let firstBraceIndex = originalString.rangeOfString("{")`, say `let firstBraceIndex = (originalString as NSString).rangeOfString("{")`. Now you have an NSRange and can carry on from there. – matt Oct 04 '15 at 18:14

2 Answers2

8

If you start with your original string cast as a Cocoa NSString:

var originalString = "Hello {world} and those who inhabit it." as NSString

... then your range results will be NSRange and you'll be able to hand them back to Cocoa.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • And see my book, which goes into this topic in some detail: http://www.apeth.com/swiftBook/ch03.html#_string – matt Oct 04 '15 at 18:06
  • No way to do this with a native Swift String? – Jordan H Oct 04 '15 at 18:15
  • This _is_ the way to do it with a native Swift string. I don't see what you're after. I've solved the problem for you. Do you need me to rewrite your whole code and prove it? Just try what I said. – matt Oct 04 '15 at 18:16
  • 19
    It might be the way to do it, but involves bridging. It doesn't *quite* use String, and it definitely doesn't use Swift Ranges. It worked for me, but there's more than a whiff of "workaround" to it. It's perfectly clear what bothers him about the solution; there's no need to be abrasive about it. – The Cappy Dec 14 '15 at 01:58
4

To make an NSRange you need to get the starting location and length of the range as ints. You can do this using the distance(from:to:) method on your originalString:

let rangeStartIndex = firstBraceIndex!.lowerBound
let rangeEndIndex = firstClosingBraceIndex!.upperBound

let start = originalString.distance(from: originalString.startIndex, to: rangeStartIndex)
let length = originalString.distance(from: rangeStartIndex, to: rangeEndIndex)

let nsRange = NSMakeRange(start, length)

let attributedString = NSMutableAttributedString(string: originalString)
attributedString.addAttributes([NSFontAttributeName: boldFont], range: nsRange)

To get the startingLocation, get the distance from the originalString's startIndex, to the starting index of the range you want (which in your case would be the firstBraceIndex.lowerBound if you want to include the {, or firstBraceIndex.upperBound if you don't). Then to get the length of your range, get the distance from your range's starting index to its ending index.

I just force unwrapped your firstBraceIndex and firstClosingBraceIndex to make the code easier to read, but of course it would be better to properly deal with these potentially being nil.

Marcus
  • 839
  • 8
  • 14