I have done some research and found this post: Add placeholder text inside UITextView in Swift?
Incorporating the above example in my code, I have the following in a blank xcode UIKit project:
import UIKit
class ViewController: UIViewController {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
sampleTextView.delegate = self
sampleTextView.text = placeholderText
sampleTextView.textColor = placeholderTextColor
sampleTextView.becomeFirstResponder()
sampleTextView.selectedTextRange = sampleTextView.textRange(from: sampleTextView.beginningOfDocument, to: sampleTextView.beginningOfDocument)
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
sampleTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
}
}
extension ViewController: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = placeholderText
textView.textColor = placeholderTextColor
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Combine the textView text and the replacement text to
// create the updated text string
let currentText:String = textView.text
let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
// If updated text view will be empty, add the placeholder
// and set the cursor to the beginning of the text view
if updatedText.isEmpty {
textView.text = placeholderText
textView.textColor = placeholderTextColor
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
// Else if the text view's placeholder is showing and the
// length of the replacement string is greater than 0, set
// the text color to black then set its text to the
// replacement string
else if textView.textColor == placeholderTextColor && !text.isEmpty {
textView.textColor = normalTextColor
textView.text = text
}
// For every other case, the text should change with the usual
// behavior...
else {
return true
}
// ...otherwise return false since the updates have already
// been made
return false
}
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == placeholderTextColor {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
}
But I have two bugs with this that for the life of me, I can't work out:
Apologies if these are basic but I'm stumped.
Couple notes...
I think the Shift is not being released because you return False
from shouldChangeTextIn range
... so the textView doesn't fully communicate with the keyboard.
As to the duplicated predictive text... I've run across similar issues. My impression is that the text is already inserted and a new .selectedRange
is set by the time shouldChangeTextIn range
is called. So the code (as written) duplicates it.
Because of those (and other) complications, here's an example of using a CATextLayer
as the "placeholder" text:
class PlaceHolderTestViewController: UIViewController, UITextViewDelegate {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
let textLayer = CATextLayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
textLayer.string = placeholderText
if let fnt = sampleTextView.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
sampleTextView.layer.insertSublayer(textLayer, at: 0)
// set delegate to self
sampleTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update textLayer frame here
textLayer.frame = sampleTextView.bounds.insetBy(
dx: sampleTextView.textContainerInset.left + sampleTextView.textContainer.lineFragmentPadding,
dy: sampleTextView.textContainerInset.top
)
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
and, here's an example of subclassing UITextView
to make it easier to reuse -- as well as easily allowing more than one textView at a time:
class PlaceholderTextView: UITextView, UITextViewDelegate {
private let textLayer = CATextLayer()
public var placeholderText: String = "" {
didSet {
textLayer.string = placeholderText
setNeedsLayout()
}
}
public var placeholderTextColor: UIColor = .lightGray {
didSet {
textLayer.foregroundColor = placeholderTextColor.cgColor
setNeedsLayout()
}
}
override var font: UIFont? {
didSet {
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
layer.insertSublayer(textLayer, at: 0)
// set delegate to self
delegate = self
}
override func layoutSubviews() {
super.layoutSubviews()
textLayer.frame = bounds.insetBy(
dx: textContainerInset.left + textContainer.lineFragmentPadding,
dy: textContainerInset.top
)
}
func textViewDidChange(_ textView: UITextView) {
// show / hide the textLayer
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
}
class PlaceHolderTestViewController: UIViewController {
let sampleTextView = PlaceholderTextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
sampleTextView.textColor = normalTextColor
sampleTextView.placeholderTextColor = placeholderTextColor
sampleTextView.placeholderText = placeholderText
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
Note -- this is Example Code Only and should not be considered "Production Ready."
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.