0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UITextViewの一部のテキストに背景色を角丸でつける

Last updated at Posted at 2021-01-09

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

スクリーンショット 2021-01-09 22.18.13.png

背景色を角丸でつける

この背景色の部分に角丸をつけたい場合は、NSLayoutManagerfillBackgroundRectArray メソッドをオーバーライドして背景を角丸で描画するカスタムクラスを作り、UITextViewに適用します。

TextLayout.swift
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)

スクリーンショット 2021-01-09 21.33.21.png

背景色を角丸でつける(改行も考慮する)

上記の方法の場合、背景色のつくテキストが改行されると次のように折り返しの部分も角丸になってしまいます。

スクリーンショット 2021-01-09 21.44.24.png

そこで、改行も考慮して fillBackgroundRectArray メソッドを実装してみます。実装のポイントは、

  • fillBackgroundRectArray メソッドはテキスト内の backgroundColor 属性ごとに呼び出される
  • 一つの backgroundColor 属性に対して、テキストが改行される場合は、入力の矩形 rectArray, rectCount が複数になる
  • 複数の矩形がある場合は、最初の矩形は左側のみ角丸をつけ、最後の矩形は右側のみ角丸をつけ、それ以外は角丸をつけない

という感じです。

TextLayout.swift
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()
        }
    }
}

スクリーンショット 2021-01-09 21.51.34.png

いい感じになりました :tada:

:warning: 問題点 : UITextView のスクロールが無効の場合にクラッシュする

今回のようにカスタマイズした NSLayoutManager を適用した UITextView のスクロールが無効 (isScrollEnabled = false) になっていると、UITextView の描画時にクラッシュしてしまいます。

rdar://14568899 (Turning off text view scrolling crashes layout manager)

この問題の解消方法が今のところわかっていません :cry:

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?