どうも、現役iOSエンジニアの武久 なおきです!
SwiftUIを使って実装をしているといつも思うのですが、
「TextEditorって使いづらい」
ですよね。
.....カスタマイズ性が低すぎる!!
Twitterみたいに、文字数制限を超えると何か変化するTextEditorが欲しい!!

↑こちらのように文字の背景が赤くなる仕様は、純正では頑張っても作れませんでした。
※もし純正のTextEditorで作れる場合はこっそり教えてください(:
なので...もう。
自分で作っちゃいませんか?TextEditor
実は意外と簡単にTextEditorって作れちゃうんですよね。
魔法の「NSAttributedString」で。
※注意※ 今回はSwiftUIでの実装ではなく基礎のStroyboardで実装します。
基本的な実装内容としてはStroyboardでもSwiftUIでもUIKitを使うことには変わりないので、SwiftUIでの実装は要望が多ければ掲載します。
NSAttributedStringとは
NSAttributedStringは、テキストに対して異なる属性(フォント、色、スタイルなど)を付けることができる文字列を表すクラスです。テキストの特定の範囲に対して異なる外観を持たせる場合に使用されます。
NSMutableAttributedString
NSMutableAttributedStringは、NSAttributedStringクラスの派生クラスであり、テキストに対して異なる属性(フォント、色、スタイルなど)を付けることができる文字列を表すクラスです。NSAttributedString同様に、テキストの特定の範囲に対して異なる外観を持たせるために使用されます。ただし、NSMutableAttributedStringは、不変オブジェクトであるNSAttributedStringとは異なり、テキストや属性を後から変更できる点が異なります。
NSMutableAttributedString公式ドキュメント
Storyboardで作成してみる
1.まずはContainerViewを作成します。
2.作成されたContainerViewの中にTextViewを差し込みます。
3.オートレイアウトを設定します
Storyboardは以上!
NSMutableAttributedStringを使ってみよう!
1.TextViewDelegateを準拠させてtextViewDidChangeを使用する。
2.textViewDidChangeの中でまずはtextViewをアンラップし、文字数制限の数字を定義します。
// テキストViewのオプショナルをアンラップします
if let text = textView.text {
// Twitter(X)と同じ140文字制限
// 正しくは全角140文字、半角280文字だが割愛します
// 今回はテストなので5文字制限にします
let characterLimit = 5
3.次に、アンラップしたtextのカウントが制限文字数を超えていないか分岐させます。そして分岐の中でNSMutableAttributedStringに変換したtextを定義します。
// 制限を超えた場合
if text.count > characterLimit {
// 受け取った文字列を属性付き文字列として変換し代入
let attributedString = NSMutableAttributedString(string: text)
4.NSRangeを使い、制限文字数以降の文字列を取得します。
※NSRangeは、Foundationフレームワークに含まれる、テキスト文字列内の範囲を表すための型です
NSRange公式ドキュメント
// 文字数制限以降の文字の範囲を取得
let range = NSRange(location: characterLimit, length: text.count - characterLimit)
5.取得した範囲の文字列の背景を赤に変更します。そしてtextViewのattributedTextに代入します。
// 作成した属性付き文字列に変更を加える(今回は140字以降は背景色が赤になる)
attributedString.addAttribute(.backgroundColor, value: UIColor.red, range: range)
// スタイル付きのテキスト、attributedTextプロパティに変更を加えたものを代入
textView.attributedText = attributedString
以上です!
意外と簡単にtextViewに属性や装飾をつけることができるんですね!(:
おっと...viewDidLoadにtextView.delegate = selfなどを記述することも忘れないでくださいね!
今回実際に作った際の全体コード
//
// BackgroundRedTextEditorController.swift
// CustomTextEditor
//
// Created by 武久 なおき on 2023/07/26.
//
import Foundation
import UIKit
/// 背景カスタムエディタ.
class BackgroundRedTextEditorController: UIViewController, UITextViewDelegate {
// テキストエディター
@IBOutlet weak var backgroundRedText: UITextView!
// プレースホルダ
@IBOutlet weak var placeholder: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// 初期値を代入
backgroundRedText.text = ""
// プレースホルダーに表示する内容を代入
placeholder.text = "文字数を超過すると背景が赤くなります"
// UITextViewのDelegateを設定している
backgroundRedText.delegate = self
// オートレイアウトを使用するためfalse
backgroundRedText.translatesAutoresizingMaskIntoConstraints = false
// 親にViewを追加する
view.addSubview(backgroundRedText)
view.addSubview(placeholder)
}
func textViewDidChange(_ textView: UITextView) {
if textView.text == "" {
placeholder.text = "文字数を超過すると背景が赤くなります"
} else {
placeholder.text = ""
}
// テキストViewのオプショナルをアンラップします
if let text = textView.text {
// Twitter(X)と同じ140文字制限
// 正しくは全角140文字、半角280文字だが割愛します
let characterLimit = 5
// 制限を超えた場合
if text.count > characterLimit {
// 受け取った文字列を属性付き文字列として変換し代入
let attributedString = NSMutableAttributedString(string: text)
// 文字数制限以降の文字の範囲を取得
let range = NSRange(location: characterLimit, length: text.count - characterLimit)
// 作成した属性付き文字列に変更を加える(今回は140字以降は背景色が赤になる)
attributedString.addAttribute(.backgroundColor, value: UIColor.red, range: range)
// スタイル付きのテキスト、attributedTextプロパティに変更を加えたものを代入
textView.attributedText = attributedString
// 制限を超えていない場合
} else {
// 受け取った文字列を属性付き文字列として変換し代入
let attributedString = NSAttributedString(string: text)
// そのままattributedTextプロパティへ代入
textView.attributedText = attributedString
}
}
}
}
他にもいろんなTextEditorが作れちゃうのでお試しあれ✨