As the title says - it's trivial to add a placeholder to UITextView
.
Simply use .textDidChangeNotification
and add a CATextLayer
or even just a UILabel
.
The problem is:
absolutely, exactly, definitively finding the identical position so that the placeholder is always precisely where the main text is.
(Thus, if your placeholder text is "Add your comment" you should be able to type "Add your comment" and it's absolutely identical.)
Of course, you can trivially set the text font/etc the same, but how to absolutely match the positioning, even as Apple inevitably slightly moves things around inside UITextView, and depending on what it is inside, it's own size etc etc.
How to?
The only way I know to do it:
import UIKit
class PlaceholderTextView: UIITextView {
var placeholderText: String = "" {
didSet { placeholderLabel.text = placeholderText }
}
override func common() {
super.common()
clipsToBounds = true
layer.cornerRadius = 5.0
NotificationCenter.default.addObserver(
self, selector: #selector(tickle),
name: UITextView.textDidChangeNotification,
object:nil)
}
@objc func tickle() {
placeholderLabel.isHidden = !text.isEmpty
}
and then surprisingly ...
lazy private var placeholderLabel: UIITextView = {
// seems strange, but the ONLY way to truly match the position
// at all times and in all ways, is use another text view
let p = UIITextView()
// !NOTE! use the same base as PlaceholderTextView does,
// example, YourClientSpecificTextView, etc.
p.isEditable = false
p.isUserInteractionEnabled = false
p.backgroundColor = .clear
p.font = font
p.textColor = .placeholderText // NOTE
addSubview(p)
return p
}()
and then, it's trivial to set the position of the ghosty text view:
(Don't forget to copy the textContainerInset; the textContainerInset is almost always changed in apps.)
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.textContainerInset = textContainerInset
placeholderLabel.frame = bounds
// conceivably, the font may have been changed dynamically:
placeholderLabel.font = font
placeholderLabel.textColor = .placeholderText // NOTE
tickle()
}
}
@objc func tickle() {
placeholderLabel.isHidden = !text.isEmpty
}
change to
@objc func tickle() {
placeholderLabel.isHidden = false //!text.isEmpty
}
then perform this test:
Thus, if your placeholder text is "Add your comment" you should be able to type "Add your comment" and it's absolutely identical.
Example of exact match:
What is the common()
initializer in the example class? In projects, I just add trivial initializing views for the various iOS views to save typing. (Notice the extra "I" for initializing.)
So:
import UIKit
class UIITextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
func common() {}
}
Example,