はじめに
こんにちは
UITextFieldやUITextViewなどで以下のように一部分だけ色をつける実装について少し悩んだので共有してみたいと思います。至らぬ点など多々あると思いますが、コメントなど頂けたら幸いです。
textColorについて
UITextFieldやUITextViewにはtextColor
というプロパティがあります。
このプロパティにUIColor
を指定することで文字の色を変更することができますが、以下のように文字の色全てが変わってしまいます。
そこで今回のような実装の場合はUITextFieldやUITextViewがプロパティとして持つNSAttributedStringを利用して実装します。
サンプルコード
StoryboardにてUITextFieldとUIButtonを3つ置き、IB接続しています。
import UIKit
class MainViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
var typingColor = UIColor.black
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
typingColor = textField.tintColor
}
@IBAction func blackAction(_ sender: Any) {
typingColor = UIColor.black
//キャレットの色を変更
textField.tintColor = typingColor
//文字に色を設定
textField.typingAttributes?.merge([NSForegroundColorAttributeName: typingColor])
//文字を選択されている状態でボタンを押された時の処理
if textField.isTextSelected {
if let range = textField.selectedTextNSRange {
guard let attributedText = textField.attributedText else { return }
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.addAttributes([NSForegroundColorAttributeName: typingColor], range: range)
textField.attributedText = mutable
}
}
}
@IBAction func blueAction(_ sender: Any) {
typingColor = UIColor(hex: 0x4990E2)
textField.tintColor = typingColor
textField.typingAttributes?.merge([NSForegroundColorAttributeName: typingColor])
if textField.isTextSelected {
if let range = textField.selectedTextNSRange {
guard let attributedText = textField.attributedText else { return }
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.addAttributes([NSForegroundColorAttributeName: typingColor], range: range)
textField.attributedText = mutable
}
}
}
@IBAction func greenAction(_ sender: Any) {
typingColor = UIColor(hex: 0x7ED321)
textField.tintColor = typingColor
textField.typingAttributes?.merge([NSForegroundColorAttributeName: typingColor])
if textField.isTextSelected {
if let range = textField.selectedTextNSRange {
guard let attributedText = textField.attributedText else { return }
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.addAttributes([NSForegroundColorAttributeName: typingColor], range: range)
textField.attributedText = mutable
}
}
}
//TextFieldの内容に変化があった時に呼ばれます
//これが無いと、文字を消した時にキャレットが直前の色になってしまいます
func textFieldDidChange(_ textField: UITextField) {
textField.typingAttributes?.merge([NSForegroundColorAttributeName: typingColor])
}
}
extension UITextInput {
//NSRangeとして取得します
var selectedTextNSRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
//文字が選択されているかどうか
var isTextSelected: Bool {
guard let range = selectedTextRange else { return false }
return !range.isEmpty
}
}
extension UIColor {
convenience init(hex: Int, alpha: CGFloat = 1.0) {
let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0
let green = CGFloat((hex & 0xFF00) >> 8) / 255.0
let blue = CGFloat(hex & 0xFF) / 255.0
let a = alpha
self.init(red: red, green: green, blue: blue, alpha: a)
}
}
extension Dictionary {
mutating func merge<S: Sequence>(_ other: S) where S.Iterator.Element == (key: Key, value: Value) {
for (key, value) in other {
self[key] = value
}
}
}
※若干単調になってしまい、申し訳ございません。(><)
-
UITextFieldやUITextViewが持つ
typingAttributes
というプロパティに対して、文字色やフォントの情報を追加設定しています。 -
selectedTextRange
のプロパティを用いることで、テキストを選択してボタンを押した場合に選択されている部分の色を変更できるようにしています。 -
textFieldDidChange
メソッドにてテキスト変更時にtypingAttributes
を設定し、色のついた文字を消した時の直前の色とキャレットの色を合わせています。
UITextRangeのisEmptyについて
サンプルではExtensionで分かりやすく書き直してみましたが正確には以下のようになっているみたいです。
さいごに
今回の例ではUITextFieldを利用させていただきましたが、UITextViewでの実装もほとんど変わりません。UITextFieldにはtextFieldDidChange
がUITextFieldDelegateに無かったのでIBで接続するかaddTargetする方法にしました。
参考にさせていただいた記事
見て頂いてありがとうございます。