UITextViewのプレースホルダを追加したい
概要
UITextFieldはプレースホルダをつけられるけど、UITextViewはつけられないので
完成品
//
// PlaceHolderedTextView.swift
// PlaceHolderedTextView
//
// Created by はるふ on 2016/11/29.
// Copyright © 2016年 はるふ. All rights reserved.
//
import UIKit
@IBDesignable class PlaceHolderedTextView: UITextView {
@IBInspectable var placeHolder: String = ""
@IBInspectable var placeHolderColor: UIColor = .lightGray
private var placeHolderLayer: CATextLayer?
private func createPlaceHolderLayerIfNeed() {
if placeHolderLayer == nil {
let layer = CATextLayer()
layer.fontSize = self.font?.pointSize ?? UIFont.systemFontSize
layer.frame = CGRect(x: self.textContainerInset.left + self.textContainer.lineFragmentPadding, y: self.textContainerInset.top, width: self.frame.width, height: layer.fontSize+10)
layer.string = self.placeHolder
layer.foregroundColor = placeHolderColor.cgColor
layer.contentsScale = UIScreen.main.scale
layer.alignmentMode = kCAAlignmentLeft
self.layer.addSublayer(layer)
placeHolderLayer = layer
}
}
private func removePlaceHolderLayerIfNeed() {
placeHolderLayer?.removeFromSuperlayer()
placeHolderLayer = nil
}
private func updateLayer() {
// Observerから呼ばれるとmainじゃないかも?
DispatchQueue.main.async {
if self.text == nil || self.text.isEmpty {
self.createPlaceHolderLayerIfNeed()
} else {
self.removePlaceHolderLayerIfNeed()
}
}
}
func onChangedText(_ notification: NSNotification?) {
updateLayer()
}
// MARK: Observer関連
private func addObserver() {
updateLayer()
NotificationCenter.default.addObserver(self, selector: #selector(self.onChangedText(_:)), name: NSNotification.Name.UITextViewTextDidChange, object: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addObserver()
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
addObserver()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
設定方法
- 新規ファイルを作成し、上のコードをコピペ
- StoryboardにUITextViewを配置する
- UITextViewを選択し、クラスを"PlaceHolderedTextView"に設定
以上でStoryboardから編集できるようになります
目指した理想形
探すとすぐに記事が出てきて、だいたい同じようなコードがあるが、
以下の点を鑑み、自分で作ろうと思った。
コードはViewに完結させたい
ViewControllerからいじるほどでもないし、使い回しも考えると、Viewのコードだけで実現できる方が良い
Observerを使いたくなかった(実現できなかった)
実現できなかったが・・・
override var text: String! {
didSet {
// ここでupdate
}
}
できるかと思ったら、できなかった。微妙なタイミングでしか呼ばれない。
delegateを使うと、ViewController側で使えなくなるので、使わなかった
うまく取れる方法があれば、教えていただきたい。
UILabelまでいらない
わざわざUILabelほどリッチなものはいらないと思った。
極論、一つのViewに完結したコードなので、CoreTextでも良い。
今回はCATextLayerを使いました。
drawでupdateされるコードが多い
これが結構よくわからなかったが、何故かdrawで更新するコードが多い。
func draw(_ rect: CGRect) {
}
でUILabelとかCALayerとかに変更を加えるのは、目的に合ってない。ここは、CoreTextなどを使った場合に描画する所(だと思う)
@IBDesignable, @IBInspectable
Storyboardからプレースホルダの色・文字を選択できる方が良い
表示だけなら、ビルドした後は反映されるっぽい
(triggerとか設定しなくて良いっぽい)
プレースホルダの位置
self.textContainer.lineFragmentPadding
(textViewの両端にある5pxぐらいのpadding)
self.textContainerInset
(textViewの上下にある8pxぐらいのpadding)
を考慮する必要がある。
でも、これでも何故か下に1, 2pxぐらいずれている気がします・・・CATextLayerにpaddingがあるのかな?と思いますが、詳しい方いれば教えていただきたいです。
おまけ:編集開始したら消す
色の設定によると思いますが、編集開始時にプレースホルダを消したい場合は updateLayer()
を以下のように書き換えます
private func updateLayer() {
// Observerから呼ばれるとmainじゃないかも?
DispatchQueue.main.async {
if (self.text == nil || self.text.isEmpty) && !self.isFirstResponder {
self.createPlaceHolderLayerIfNeed()
} else {
self.removePlaceHolderLayerIfNeed()
}
}
}
Author And Source
この問題について(UITextViewのプレースホルダを追加したい), 我々は、より多くの情報をここで見つけました https://qiita.com/_ha1f/items/15dc1337d4935b8eccb4著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .