mono0926
9/30/2016 - 8:06 AM

string.nsrange.swift

// こういう相互変換が正しそう
extension String {
    func nsRange(from range: Range<String.Index>) -> NSRange {
        let from = range.lowerBound.samePosition(in: utf16)
        let to = range.upperBound.samePosition(in: utf16)
        return NSRange(location: utf16.distance(from: utf16.startIndex, to: from),
                       length: utf16.distance(from: from, to: to))
    }
    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
    }
}

// 利用例

let str = "a👿b🇩🇪c"
let r1 = str.range(of: "🇩🇪")!
// → Index(_base: Swift.String.UnicodeScalarView.Index(_position: 4), _countUTF16: 4)..<Index(_base: Swift.String.UnicodeScalarView.Index(_position: 8), _countUTF16: 1)
// (意味的には、「startIndexから3進めたところ..<startIndexから4進めたところ」だが、UTF16でcountされている)
// https://github.com/apple/swift/blob/master/stdlib/public/core/StringCharacterView.swift#L159-L191

// Range → NSRange:
let n1 = str.nsRange(from: r1)
print((str as NSString).substring(with: n1)) // 🇩🇪

// コードでNSRangeを生成したが、実際のアプリではUIKitからこういう範囲指定されたNSRangeが渡ってくる

// NSRange → Range<Int>
let r2 = n1.toRange()! // 標準メソッドでの変換は4..<8 となりNG
print(str[sequentialAccess: r2]) // 範囲外でnil (正解は🇩🇪)
// NSRange → 正しく計算したRange<Index>
let r3 = str.range(from: n1)!
print(str[r3]) // 🇩🇪