iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

NSDataDetector を使う際に少し気を付けるところ

今回は、NSDataDetector を使って特定の文字列を抽出する時に少しハマったポイントを紹介したいと思います。

ハマりポイント

いつもの如く NSDataDetector を使用して、文字列の中に存在するリンクを下記のように取得していると、意図しない場所でリンクがきれて読み込まれてしまうという問題が発生しました。

func getLinkTextList(text: String) -> [String] {
    guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
        return []
    }
    let enableLinkTuples = detector.matches(in: text, range: NSRange(location: 0, length: text.count))
    return enableLinkTuples.map { checkingResult -> String in
        return (text as NSString).substring(with: checkingResult.range)
    }
}

let linkString = getLinkTextList(text: "僕の記事はこちら🙃👨<200d>💻🍎✨🎣 https://yamato8010.hatenablog.com/").first!

print(linkString) // 出力: https://yamato8010.hatenabl <- 変なところできれてる!

原因としては、絵文字などが入っている場合に、NSDataDetector がパースする際の文字列サイズと、text.count で取得していた文字列のサイズが合わずに途中までしかリンクが読み込まれないというものでした✍️

恐らく、NSString が、UTF-16 コードユニットのシーケンスとして表示されるため、Objective-C 由来の NSDataDetector も UTF-16 でパースされるのが原因かと思われます🤔

developer.apple.com

解決

text.counttext.utf16.count に変えて実行すると正常にリンクを表示することができるようになりました🎉

func getLinkTextList(text: String) -> [String] {
    guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
        return []
    }
    let enableLinkTuples = detector.matches(in: text, range: NSRange(location: 0, length: text.utf16.count))
    return enableLinkTuples.map { checkingResult -> String in
        return (text as NSString).substring(with: checkingResult.range)
    }
}

let linkString = getLinkTextList(text: "僕の記事はこちら🙃👨<200d>💻🍎✨🎣 https://yamato8010.hatenablog.com/").first!

print(linkString) // 出力: https://yamato8010.hatenablog.com/

余談

文字コードに下記の記事を参考にまとめました✍️

https://qiita.com/yamoridon/items/6f73ffe02ab46eb6ae85

property 内容
String.utf16.count 2バイトを1単位として、1つの文字番号を1単位(2バイト)または2単位(4バイト)の可変長で文字サイズ(単位)を取得します。NSString などの文字サイズはこの utf16 での単位数になります。
String.unicodeScalars.count 文字のサイズを utf32 で表します。utf32 は1単位を4バイトの固定長として文字番号を扱えるようにしたもので、これにより全ての文字番号を1単位で扱えるようになります。
String.utf8.count 英数文字のような頻繁に使用される文字は1バイトで、マイナーな文字を複数バイトで扱うようにすることで、1バイトを1単位として、1~4単位の可変長で文字サイズを取得できるようにします。これによって、容量を無駄に使用する英数字などを使う際に容量を無駄に使う必要がなくなります。
String.count UI に表示される見た目の文字数で、Unicode によって Grapheme Cluster として定義されている形式

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com