[Swift] NSRange と Range の使い方

Swift

Swift で String を使うとでてくる Range と NSAttributedString を扱う時に必要な NSRange をまとめます

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.1 Beta
  • Xcode 13.1
  • iOS 15

Range と NSRange

Range

String や AttributedString 中の部分文字列を使用したり、確認したりする時に使うのが Range です。

String に特定文字列が含まれているか確認する例


let str = "Hello, world!"
if let range = str.range(of: "llo") {
    print("\(str) has \(str[range])")
}
// output
Hello world has llo

ちなみに、どんなデータを持っているかと lowerBound, upperBound を確認してみると以下のようになっています。


let str = "Hello world"
let range = str.range(of: "llo")!
// (1)
print(str[range])
// (2)
print(range.lowerBound)
print(range.upperBound)

// print-out
llo                      // str[range]
Index(_rawBits: 131072)  // range.lowerBound
Index(_rawBits: 327680)  // range.upperBound
コード解説
  1. Range を使って、subscriptアクセス([ ]のこと)すると、該当部分の文字列を取得することができます。
  2. Range のプロパティ lowerBound, upperBound をみても直感的にはわかりませんでした・・・

NSRange

NSRange も Range と同様に、NSString 中の部分文字列を利用したりする時に使われるものです。

NSString, NSRange の方が、String, Range よりも古くからあります。(String は、Swift にしか存在しないです)


let nsStr = NSString("Hello world")
let nsRange = nsStr.range(of: "llo")
// (1)
print(nsStr.substring(with: nsRange))
// (2)
print(nsRange)

// print-out
llo    // nsStr.substring
{2, 3} // nsRange

コード解説
  1. String と違い、subscript で部分文字列を取得することはできませんが、substring というメソッドが用意されています
  2. NSRange は 開始位置と文字列長という情報を持っているようです。文字列の2番目の文字(0スタートです)から3文字分ということを表しています

経緯

なんで 同じような型があるの? というのが最初の疑問点かと思います。

ソフトウェアでの文字列処理には長い歴史がありますので、詳細は別途調べてもらうとして、簡単にいうと いろいろな文字を処理できるようにしたところ、1文字 = 可変長 になってしまった。

そのため、当初 NSRange で扱っていたような Int を使った "X文字目" から "Y文字分" のような扱いができなくなってしまったということ(のよう)です。

1文字 = 1 char であれば、String 中の i 番目の文字の次の文字は、(i+1) 番目になりますが、可変長になってしまうと、単純に +1 しても NG です。

そこで Swift での String には、Range という新しい String 中の部分文字列を指定する型が作られました。

例えば、Swift で String 中の2文字目から3文字分を取得するためは以下のようになり、単純に +3 して・・・ という計算ではなくなっています。


let str = "Hello world"
let secondCharIndex = str.index(str.startIndex, offsetBy: 1)
let afterThreeCharIndex = str.index(secondCharIndex, offsetBy: 3)
print(str[secondCharIndex..<afterThreeCharIndex])
// print-out
ell

Range と NSRange の相互変換

Swift で使っている時に String と Range だけで済むと平和です。

ですが、NSString を使う必要が出てくるケースで、NSRange が必要となることがあります。例えば、NSTextView を NSAttributedString を使って、装飾表示するケースです。

そんな時に、Range を NSRange に変換したり、その逆の NSRange を Range に変換したりすることが必要になります。

それぞれに、initializer が用意されています。どちら方向の変換でも 対象としている String も渡す必要があります。

Range を NSRange に変換する


let str = "Hello world"
let range = str.range(of: "llo")!
let nsRange = NSRange(range, in: str)   // Range -> NSRange
print(nsRange)
// print-out
{2, 3}

NSRange を Range に変換する


let nsStr = NSString("Hello world")
let nsRange = nsStr.range(of: "llo")
let range = Range(nsRange, in: nsStr as String)
print(range)
// print-out
Index(_rawBits: 131072)..<Index(_rawBits: 327680)

まとめ:Range と NSRange の相互変換

Range と NSRange の相互変換
  • Range と NSRange は、initializer で相互変換できる
  • 相互変換時には、対象とした String/NSString が必要

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です