UITextViewの一部のテキストに背景色を角丸でつける方法です。
背景色をつける
UITextView のテキストの一部に背景色をつけたい場合は、NSAttributedString + NSAttributedString.Key.backgroungColor 属性を使います。
let text = "あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、..."
let attributedText = NSMutableAttributedString(string: text)
attributedText.addAttributes([.backgroundColor: UIColor.cyan], range: NSRange(location: 2, length: 7))
let textView = UITextView()
textView.attributedText = attributedText
背景色を角丸でつける
この背景色の部分に角丸をつけたい場合は、NSLayoutManager の fillBackgroundRectArray メソッドをオーバーライドして背景を角丸で描画するカスタムクラスを作り、UITextViewに適用します。
import UIKit
class LayoutManager: NSLayoutManager {
var backgroundCornerRadius: CGFloat = 0
override func fillBackgroundRectArray(_ rectArray: UnsafePointer<CGRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: UIColor) {
guard let context = UIGraphicsGetCurrentContext() else {
super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)
return
}
context.saveGState()
defer { context.restoreGState() }
context.setFillColor(color.cgColor)
for rectIndex in 0..<rectCount {
let rect = rectArray.advanced(by: rectIndex).pointee
let path = UIBezierPath(roundedRect: rect, cornerRadius: backgroundCornerRadius)
context.addPath(path.cgPath)
context.fillPath()
}
}
}
let layoutManager = LayoutManager()
layoutManager.backgroundCornerRadius = 5
let textView = UITextView()
textView.textContainer.replaceLayoutManager(layoutManager)
背景色を角丸でつける(改行も考慮する)
上記の方法の場合、背景色のつくテキストが改行されると次のように折り返しの部分も角丸になってしまいます。
そこで、改行も考慮して fillBackgroundRectArray メソッドを実装してみます。実装のポイントは、
- fillBackgroundRectArray メソッドはテキスト内の backgroundColor 属性ごとに呼び出される
- 一つの backgroundColor 属性に対して、テキストが改行される場合は、入力の矩形
rectArray
,rectCount
が複数になる - 複数の矩形がある場合は、最初の矩形は左側のみ角丸をつけ、最後の矩形は右側のみ角丸をつけ、それ以外は角丸をつけない
という感じです。
class LayoutManager: NSLayoutManager {
var backgroundCornerRadius: CGFloat = 0
override func fillBackgroundRectArray(_ rectArray: UnsafePointer<CGRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: UIColor) {
guard let context = UIGraphicsGetCurrentContext() else {
super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)
return
}
context.saveGState()
defer { context.restoreGState() }
context.setFillColor(color.cgColor)
let cornerRadii = CGSize(width: backgroundCornerRadius, height: backgroundCornerRadius)
for rectIndex in 0..<rectCount {
let rect = rectArray.advanced(by: rectIndex).pointee
let corners: UIRectCorner
if rectCount == 1 {
corners = .allCorners
} else if rectIndex == 0 {
corners = [.topLeft, .bottomLeft]
} else if rectIndex == rectCount - 1 {
corners = [.topRight, .bottomRight]
} else {
corners = []
}
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
context.addPath(path.cgPath)
context.fillPath()
}
}
}
いい感じになりました
問題点 : UITextView のスクロールが無効の場合にクラッシュする
今回のようにカスタマイズした NSLayoutManager を適用した UITextView のスクロールが無効 (isScrollEnabled = false
) になっていると、UITextView の描画時にクラッシュしてしまいます。
rdar://14568899 (Turning off text view scrolling crashes layout manager)
この問題の解消方法が今のところわかっていません