NSTextAttachment で TextView に画像をつける方法・NSTextAttachment の画像を加工する方法

4 min read読了の目安(約4000字

NSTextAttachment について

NSTextAttachment は NSAttributedString などで TextView の attributedText に画像などを入れたい時に利用する class になります。

自分は NSTextAttachment を使って、TextView に画像を入れたり、TextView に入れる画像を加工したりしたので、その実装方法について説明していきます。

NSAttributedString の詳しい使い方については以前記事に書いたので、気になる方はそちらを参照していただければと思います🙏

TextView に画像を入れる方法

まず、TextView に画像を入れる方法について説明します。

基本的な流れとしては、

  • 画像や文字をセットしたい NSMutableAttributedString などを用意しておく
  • NSTextAttachment に image をセットする
  • NSAttirubtedString(attachment:) を利用して、用意しておいた NSTextAttachment を利用した NSAttributedString を作る
  • 用意しておいた NSMutableAttributedString の任意の位置に作成した NSAttributedString を insert などする
  • UITextView の attirubtedText に NSAttributedString をセットする

実際にコードで説明すると以下のようなイメージになります

// 画像や文字をセットしたい NSMutableAttributedString を用意しておく
let attributedString = NSMutableAttributedString(string: "")

let imageAttachment = NSTextAttachment()
// imageAttachment の大きさなどを指定する(centerYTextView については後ほど説明)
imageAttachment.bounds = CGRect(x: 0, y: centerYTextView, width: 20, height: 20)
// 任意の UIImage をセットする
imageAttachment.image = image
let imageString = NSAttributedString(attachment: imageAttachment)

// 任意の位置に imageString を挿入する
attributedString.insert(imageString, at: 0)

// これで textView に画像が反映される
textView.attributedText = NSAttributedString(attributedString: attributedString)

基本的にはこのような形で TextView に任意の画像を入れることが可能です。

途中で出てきた centerYTextView というものについても軽く説明しておこうと思います。
centerYTextView は独自に定義した変数で、それは以下のようなものになります。

guard let textViewFont = textView.font else { return }

let centerYTextView = (textViewFont.capHeight - 20).rounded() / 2

基本的に NSTextAttachment を利用して挿入した画像は、TextView の text の baseline 上にレンダリングされてしまうため、視覚的には text の中心に画像が沿っているようには見えません。

そのため、計算を行って text の中心に画像が来るようにしています(画像は計算部分のイメージ)。

詳しくは Stack Overflow のこちらの回答で非常に詳しく説明されているため、そちらを参照いただければと思います。

NSTextAttachment の画像を加工する方法

次に NSTextAttachment の画像を加工する方法についても説明していこうと思います。

NSTextAttachment.image は UIImageView ではなく、UIImage であるため、例えば UIImageView に対して CALayer などを用いて画像を丸くしてみたり、border を付けてみたりということを簡単にすることができません。

そのため、画像を加工するためには Core Graphics などを用いて UIImage 自体を加工する必要があると思います。

参考までに自分は、「NSTextAttachment の画像を丸くして、周りに border をつける」という実装を行ったので、その実装方法を紹介したいと思います。

private extension UIImage {
    func roundedBorder() -> UIImage? {
        return UIGraphicsImageRenderer(size: CGSize(width: 20, height: 20)).image { _ in
            let imageRect = CGRect(x: 0, y: 0, width: 20, height: 20)
            let ovalPath = UIBezierPath(ovalIn: imageRect)
	    // 画像を丸く加工する
            ovalPath.addClip()
            draw(in: imageRect)
	    // 青色を stroke にセットしておく
	    UIColor.blue.setStroke()
            ovalPath.lineWidth = 1
	    // 画像の周りに青色の border をつける
            ovalPath.stroke()
        }
    }
}

// 使い方
imageAttachment.image = uiImageView.image?.roundedBorder()

上記のように、UIGraphicsImageRenderer を利用して画像の加工を行い、imageAttachment.image の部分で、作成した roundedBorder を呼び出すことによって TextView 中の画像を加工することができました。

おわりに

今回は NSTextAttachment を利用して TextView 中に画像を入れる方法、そしてその画像を加工する方法を紹介しました。
もし他に良い方法など知っている方がいればコメントなどで教えていただけると嬉しいです🙏

参考