TextKit2: Relation between NSRange and NSTextRange

The way NSTextView is built it's inevitable to use NSTextStorage with TextKit2, however the NSAttributedString uses NSRange vs the TextKit2 family uses NSTextRange for text location, etc. What I struggle with is the relation between these two. I didn't find a convenient translation between these two. Is NSAttributedStrint NSRange length=1 equal to NSTextRange offset 1? I think it's not (at least it's not necessarily true for every NSTextContentManager.

So my question is, given a NSTextRange, what is the corresponding NSRange in NSTextContentStorage.attributedString

Replies

I figured out a clumsy way to convert NSTextRange to NSRange, of cause by researching the development documents for ages.

let offset = textContentStorage!.offset(from: textLayoutManager!.documentRange.location, to: textRange.location)
let length = textContentStorage!.offset(from: textRange.location, to: textRange.endLocation)
let nsRange = NSRange(location: offset, length: length)

Found a better way yet? Having the same problem.

extension NSRange {
  init?(textRange: NSTextRange, in contentManager: NSTextContentManager) {
    let location = contentManager.offset(from: contentManager.documentRange.location, to: textRange.location)
    let length = contentManager.offset(from: textRange.location, to: textRange.endLocation)
    if location == NSNotFound || length == NSNotFound { return nil }
    self.init(location: location, length: length)
  }
}

extension NSTextRange {
  convenience init?(range: NSRange, in contentManager: NSTextContentManager) {
    guard let location = contentManager.location(contentManager.documentRange.location, offsetBy: range.location),
          let endLocation = contentManager.location(location, offsetBy: range.length) else { return nil }
    self.init(location: location, end: endLocation)
  }
}

Or you could extend NSTextContentManager:

extension NSTextContentManager {
  func range(for textRange: NSTextRange) -> NSRange? {
    let location = offset(from: documentRange.location, to: textRange.location)
    let length = offset(from: textRange.location, to: textRange.endLocation)
    if location == NSNotFound || length == NSNotFound { return nil }
    return NSRange(location: location, length: length)
  }

  func textRange(for range: NSRange) -> NSTextRange? {
    guard let textRangeLocation = location(documentRange.location, offsetBy: range.location),
          let endLocation = location(textRangeLocation, offsetBy: range.length) else { return nil }
    return NSTextRange(location: textRangeLocation, end: endLocation)
  }
}