UITextViewで複雑なハイライトを表現する
最近多くのアプリでハッシュタグやメンション、特定の文字列へのハイライトなどをUITextView
で表現したいときが多々あります。
ここではそんなときに役立つライブラリを共有したいと思います。
UITextViewで文字を装飾する
UITextView
を用いて文字を装飾したい場合にはAttributedString
で属性と値を指定することで実現します。例えば、文字を赤色に装飾したい場合には以下のようになります。
textView.text = "Hello World"
let attributedText = textView.mutableCopy() as? NSMutableAttributedString
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSMakeRange(0, 5))
textView.attributedText = attributedText
これで無事Helloの部分を赤く表示することができました。しかし、コードをよくみるといくつか辛い部分があります。
実装における問題点
- NS***AttributeNameというグローバルな値が属性のキーになっているため一覧性がない
-
addAttribute
のvalueはAny型となっているためUIColorが設定されることが自明でありながら.red
などの省略が行えない。また、どんな値でも引数に渡せてしまい安全でない。 - NSMutable~, NSRange, NS***AttributeNameなどObjective-Cの遺産を多く使用しており今後リプレースされるとしても既存実装から新たに実装が必要となる。
こういった観点からなかなか実装しにくいと言う方も、もしかしたらいるかもしれません。そういった方はHTMLで表現したりライブラリの導入検討を行なったかと思います。
また、上記の例ではシンプルに色を装飾するだけでしたが実際にはテキストの中からある特定のキーワードを個別に全てハイライトしたい場合や、キーワードごとにリンクしタップ時の挙動を柔軟に変更したいケースがほとんどです。※調べるといくつかでてきますがリンクにURL形式の文字列を埋め込み、DelegateでSchemeを検出して取得する方法もありますが文字列化されたリンクの正しい使い方と呼ぶには難しそうです。
Swift3ではUITextView
のプロパティとしてlinkTextAttributes
というものがありますが、これはリンク属性の付いたテキストの見た目を一括で装飾の指定を行うことができますが、例えば、ハッシュタグは青色、メンションは赤色など個別に対応した属性の指定を行うことはできません。
// 単語ごとに個別の指定は行えない
textView.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.red]
そこで今回上記の問題点を解決したく作成したライブラリがこちらです。
RegeributedTextView
RegeributedTextView
は、装飾の柔軟性と扱いやすさを意識し正規表現ベースで型安全なインターフェースとなるように作られています。先ほどのテキストを赤くする場合の例をRegeributedTextView
を用いて行うと次のようになります。
textView.addAttribute("Hello", attribute: .textColor(.red))
RegeributedTextView
で属性を付与する関数であるaddAttribute
は下記のように宣言されています。
func addAttribute(_ regexString: String,
attribute: TextAttribute,
values: [String: Any] = [:],
priority: Priority = .medium,
applyingIndex: ApplyingIndex = .all)
ここではより複雑な表現を指定するためのpriority
とapplyingIndex
については割愛させていただき、regexString
、attribute
とvalues
について説明したいと思います。
まずregexString
についてですが、これは属性を付与するテキストを指定する正規表現やテキスト、テンプレートをしていできます。RegeributedTextView
では主に3つのパターンで使用することができます。上から順に柔軟性が高い指定方法となっています。
パターン1
// 対象の文字列を指定する
textView.addAttribute("#general", attribute: .textColor(.red))
パターン2
// RegexStringTypeから選択する
textView.addAttribute(.hashTag, attribute: .textColor(.red))
パターン3
// 正規表現で指定する
textView.addAttribute("#[a-zA-Z0-9]", attribute: .textColor(.red))
次に引数で指定できるvalues
についてですが、これは属性を付与したテキストに持たせておきたい情報を任意で指定することが可能となっています。また、delegate
を実装することで属性の付いたテキスト(各文字・単語レベルで検出)をタップした際にvalues
で指定した値を取得することができます。
//Delegateを指定:(例)class HogeViewController: RegeributedTextViewDelegate
textView.delegate = self
// Value付きで装飾を行う
textView.addAttribute("#general", attribute: .textColor(.red), value: ["URL": "https://hoge.com"])
// 属性テキストがタップされた時に呼ばれるDelegate
func regeributedTextView(_ textView: RegeributedTextView, didSelect text: String, values: [String: Any]) {
guard let url = values["URL"] as? String else { return }
print(url)
// Do something
}
あるテキストに複数の装飾を追加したい場合には下記のようにも記述することが可能です。
textView.addAttributes("#general", attributes: [.bold, .fontSize(16), .textColor(.black)])
まとめ
-
UITextView
で複雑な装飾をしたい場合には実装が大変。 -
RegeributedTextView
では正規表現による柔軟に装飾が可能(今後Appleの仕様変更による実装のやり直しから解放される)
ライブラリについて新たに追加したい部分や提案などありましたらIssue,PRをいただけると嬉しいです。ハイライトのやり方で迷った方は一度試していただけると幸いです。
最後まで読んでいただきありがとうございました。