How can I add a placeholder in a UITextView
, similar to the one you can set for UITextField
, in Swift
?
-
This is an age old problem in iOS development with UITextView. I've written subclasses like the one mentioned here: stackoverflow.com/a/1704469/1403046 . The benefit is that you can still have a delegate, as well as use the class in multiple places without having to re-implement the logic.– cjwirthCommented Dec 26, 2014 at 3:28
-
How would I use your subclass, while using swift for the project. Using a bridge file?– StevenZCommented Dec 26, 2014 at 3:49
-
You could do that, or re-implement it in Swift. The code in the answer is longer than it really has to be. The main point being to show/hide the label you add in the method you get notified for when the text changes.– cjwirthCommented Dec 26, 2014 at 3:52
-
You can use UIFloatLabelTextView sample from GitHub. This position placeholder on top while writing. Really interesting one! github.com/ArtSabintsev/UIFloatLabelTextView– Jayprakash DubeyCommented Aug 1, 2016 at 8:14
-
3Honestly, the easiest way to accomplish this is to have a custom textView and just add placeholder text that is drawn onto the textView when no text is present.... Ever other answer so far has been a far overcomplicated version of this that involves problematic state management (including false positives for when text should/shouldn't does/doesn't exist)– TheCodingArtCommented Dec 31, 2016 at 19:09
47 Answers
Updated for Swift 4
UITextView
doesn't inherently have a placeholder property so you'd have to create and manipulate one programmatically using UITextViewDelegate
methods. I recommend using either solution #1 or #2 below depending on the desired behavior.
Note: For either solution, add UITextViewDelegate
to the class and set textView.delegate = self
to use the text view’s delegate methods.
Solution #1 - If you want the placeholder to disappear as soon as the user selects the text view:
First set the UITextView
to contain the placeholder text and set it to a light gray color to mimic the look of a UITextField
's placeholder text. Either do so in the viewDidLoad
or upon the text view's creation.
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
Then when the user begins to edit the text view, if the text view contains a placeholder (i.e. if its text color is light gray) clear the placeholder text and set the text color to black in order to accommodate the user's entry.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
Then when the user finishes editing the text view and it's resigned as the first responder, if the text view is empty, reset its placeholder by re-adding the placeholder text and setting its color to light gray.
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
Solution #2 - If you want the placeholder to show whenever the text view is empty, even if the text view’s selected:
First set the placeholder in the viewDidLoad
:
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.becomeFirstResponder()
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
(Note: Since the OP wanted to have the text view selected as soon as the view loads, I incorporated text view selection into the above code. If this is not your desired behavior and you do not want the text view selected upon view load, remove the last two lines from the above code chunk.)
Then utilize the shouldChangeTextInRange
UITextViewDelegate
method, like so:
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 = "Placeholder"
textView.textColor = UIColor.lightGray
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 == UIColor.lightGray && !text.isEmpty {
textView.textColor = UIColor.black
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
}
And also implement textViewDidChangeSelection
to prevent the user from changing the position of the cursor while the placeholder's visible. (Note: textViewDidChangeSelection
is called before the view loads so only check the text view's color if the window is visible):
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == UIColor.lightGray {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
-
7Hi, make sure you set your view controller as the delegate for your textView. You can achieve this by creating an outlet from your textView to your viewController. Then use
yourTextField.delegate = self
. If you do not do this, thetextViewDidBeginEditing
and thetextViewDidEndEditing
functions will not work. Commented Apr 23, 2016 at 17:05 -
2The code is not compiling, I am having an error as
Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')
.– iPeterCommented Jun 27, 2017 at 15:02 -
4I have found the solution and I will attach the revised code @iPeter . The current text must be in NSString formant:
let currentText = textView.text as NSString?
. Transform thelet updatedText =
line tolet updatedText = currentText?.replacingCharacters(in: range, with: text)
. Finally, transform theif updatedText.isEmpty
line toif (updatedText?.isEmpty)! {
. That should do the trick! Commented Jul 14, 2017 at 22:21 -
4@LyndseyScott setting
textView.selectedTextRange
from withinfunc textViewDidChangeSelection(_ textView: UITextView)
causes an infinite loop...– MikeGCommented Feb 5, 2019 at 18:37 -
34It's absolutely shocking that (iOS) developers should need to go to these lengths for something so simple. This is basic functionality that should have been part of the TextView control a long time ago and I can't understand why Apple hasn't addressed this yet.– KennyCommented Aug 31, 2020 at 13:01
Floating Placeholder
It's simple, safe and reliable to position a placeholder label above a text view, set its font, color and manage placeholder visibility by tracking changes to the text view's character count.
Update: Incorporated suggestions made by @RakshithaMurangaRodrigo in Feb 10, '23 comment
Swift 5:
class NotesViewController : UIViewController {
@IBOutlet var textView : UITextView!
var placeholderLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = .italicSystemFont(ofSize: (textView.font?.pointSize)!)
placeholderLabel.sizeToFit()
textView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
placeholderLabel.textColor = .tertiaryLabel
placeholderLabel.isHidden = !textView.text.isEmpty
}
}
extension NotesViewController : UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
placeholderLabel?.isHidden = !textView.text.isEmpty
}
func textViewDidEndEditing(_ textView: UITextView) {
placeholderLabel?.isHidden = !textView.text.isEmpty
}
func textViewDidBeginEditing(_ textView: UITextView) {
placeholderLabel?.isHidden = true
}
}
-
34This method is undeniably the simplest and least error-prone I've found. Fabulous.– DavidCommented Feb 3, 2015 at 2:17
-
6It is a good method but label text may go out of bound if placeholder text is very long..– AksCommented Feb 3, 2015 at 7:02
-
5Simple to use and integrates nicely with existing behavior. The placeholder message should not be so verbose anyway however wouldn't setting the lines to 0 take care of that issue? Commented Nov 4, 2015 at 13:59
-
3I generally prefer this method, though it is problematic if the text is center aligned because the cursor will be centered on top of the placeholder instead of to the left of it. Commented Jun 21, 2017 at 15:31
-
1Thank you, this is Nice and easy approach, very clean, and I could modify some what father based on this answer....
func textViewDidChange(_ textView: UITextView) { self.placeholderLabel?.isHidden = !self.textView.text.isEmpty }
func textViewDidEndEditing(_ textView: UITextView) { self.placeholderLabel?.isHidden = !self.textView.text.isEmpty }
func textViewDidBeginEditing(_ textView: UITextView) { self.placeholderLabel?.isHidden = true }
Commented Feb 10, 2023 at 3:37
Swift:
Add your text view programmatically or via Interface Builder, if the last, create the outlet:
@IBOutlet weak var yourTextView: UITextView!
Please add the delegate (UITextViewDelegate):
class ViewController: UIViewController, UITextViewDelegate {
In the viewDidLoad method, do add the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourTextView.delegate = self
yourTextView.text = "Placeholder text goes right here..."
yourTextView.textColor = UIColor.lightGray
Now let me introduce the magic part, add this function:
func textViewDidBeginEditing(_ textView: UITextView) {
if yourTextView.textColor == UIColor.lightGray {
yourTextView.text = ""
yourTextView.textColor = UIColor.black
}
}
Do note that this will execute whenever editing starts, there we will check conditions to tell the state, using the color property.
Setting text to nil
i do not recommend. Right after that, we set the text color to desired, in this case, black.
Now add this function too:
func textViewDidEndEditing(_ textView: UITextView) {
if yourTextView.text == "" {
yourTextView.text = "Placeholder text ..."
yourTextView.textColor = UIColor.lightGray
}
}
Let me insist, do not compare to nil
, i have already tried that and it would not work. We then set the values back to placeholder style, and set the color back to placeholder color because it is a condition to check in textViewDidBeginEditing
.
I am surprised that no one mentioned NSTextStorageDelegate
. UITextViewDelegate
's methods will only be triggered by user interaction, but not programmatically. E.g. when you set a text view's text
property programmatically, you'll have to set the placeholder's visibility yourself, because the delegate methods will not be called.
However, with NSTextStorageDelegate
's textStorage(_:didProcessEditing:range:changeInLength:)
method, you'll be notified of any change to the text, even if it's done programmatically. Just assign it like this:
textView.textStorage.delegate = self
(In UITextView
, this delegate property is nil
by default, so it won't affect any default behaviour.)
Combine it with the UILabel
technique @clearlight demonstrates, one can easily wrap the whole UITextView
's placeholder
implementation into an extension.
extension UITextView {
private class PlaceholderLabel: UILabel { }
private var placeholderLabel: PlaceholderLabel {
if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
return label
} else {
let label = PlaceholderLabel(frame: .zero)
label.font = font
addSubview(label)
return label
}
}
@IBInspectable
var placeholder: String {
get {
return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
}
set {
let placeholderLabel = self.placeholderLabel
placeholderLabel.text = newValue
placeholderLabel.numberOfLines = 0
let width = frame.width - textContainer.lineFragmentPadding * 2
let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
placeholderLabel.frame.size.height = size.height
placeholderLabel.frame.size.width = width
placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)
textStorage.delegate = self
}
}
}
extension UITextView: NSTextStorageDelegate {
public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
if editedMask.contains(.editedCharacters) {
placeholderLabel.isHidden = !text.isEmpty
}
}
}
Note that the use of a private (nested) class called PlaceholderLabel
. It has no implementation at all, but it provides us a way to identify the placeholder label, which is far more 'swifty' than using the tag
property.
With this approach, you can still assign the delegate of the UITextView
to someone else.
You don't even have to change your text views' classes. Just add the extension(s) and you will be able to assign a placeholder string to every UITextView
in your project, even in the Interface Builder.
I left out the implementation of a placeholderColor
property for clarity reasons, but it can be implemented for just a few more lines with a similar computed variable to placeholder
.
-
1
-
textView.textStorage.delegate = self
this in a view-controller will require us to bind that view-controller withNSTextStorageDelegate
. It is really requires?– HemangCommented Aug 8, 2019 at 8:21 -
1Simple and works like a charm. This must be accepted answer Commented Sep 14, 2019 at 8:01
-
1@Hemang it's the text view itself being
NSTextStorageDelegate
, not the view controller.– yesleonCommented Sep 19, 2019 at 15:12 -
Based on some of the great suggestions here already, I was able to put together the following lightweight, Interface-Builder-compatible subclass of UITextView
, which:
- Includes configurable placeholder text, styled just like that of
UITextField
. - Doesn't require any additional subviews or constraints.
- Doesn't require any delegation or other behaviour from the ViewController.
- Doesn't require any notifications.
- Keeps that placeholder text fully separated from any outside classes looking at the field's
text
property.
Any improvement suggestions are welcome, especially if there's any way to pull iOS's placeholder color programatically, rather than hard-coding it.
Swift v5:
import UIKit
@IBDesignable class TextViewWithPlaceholder: UITextView {
override var text: String! { // Ensures that the placeholder text is never returned as the field's text
get {
if showingPlaceholder {
return "" // When showing the placeholder, there's no real text to return
} else { return super.text }
}
set { super.text = newValue }
}
@IBInspectable var placeholderText: String = ""
@IBInspectable var placeholderTextColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0) // Standard iOS placeholder color (#C7C7CD). See https://stackoverflow.com/questions/31057746/whats-the-default-color-for-placeholder-text-in-uitextfield
private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
override func didMoveToWindow() {
super.didMoveToWindow()
if text.isEmpty {
showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
}
}
override func becomeFirstResponder() -> Bool {
// If the current text is the placeholder, remove it
if showingPlaceholder {
text = nil
textColor = nil // Put the text back to the default, unmodified color
showingPlaceholder = false
}
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
// If there's no text, put the placeholder back
if text.isEmpty {
showPlaceholderText()
}
return super.resignFirstResponder()
}
private func showPlaceholderText() {
showingPlaceholder = true
textColor = placeholderTextColor
text = placeholderText
}
}
-
I tried to use in tableview cell. When I set textView text then it does not gets set as showingPlaceholder is true by default.– NiteshCommented Mar 31, 2021 at 7:42
I did this by using two different text views:
- One in the background that is used as a placeholder.
- One in the foreground (with a transparent background) that the user actually types in.
The idea is that once the user starts typing stuff in the foreground view, the placeholder in the background disappears (and reappears if the user deletes everything). So it behaves exactly like a placeholder for the single line text field.
Here's the code I used for it. Note that descriptionField is the field the user types in and descriptionPlaceholder is the one in the background.
func textViewDidChange(descriptionField: UITextView) {
if descriptionField.text.isEmpty == false {
descriptionPlaceholder.text = ""
} else {
descriptionPlaceholder.text = descriptionPlaceholderText
}
}
-
1This way is a bit hacky but it's far and away the easiest and generates exactly the results you want. Good idea Commented Jun 11, 2017 at 18:14
I tried to make code convenient from clearlight's answer.
extension UITextView{
func setPlaceholder() {
let placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholderLabel.sizeToFit()
placeholderLabel.tag = 222
placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !self.text.isEmpty
self.addSubview(placeholderLabel)
}
func checkPlaceholder() {
let placeholderLabel = self.viewWithTag(222) as! UILabel
placeholderLabel.isHidden = !self.text.isEmpty
}
}
usage
override func viewDidLoad() {
textView.delegate = self
textView.setPlaceholder()
}
func textViewDidChange(_ textView: UITextView) {
textView.checkPlaceholder()
}
-
1A couple of issues. (1) As an extension that assumes and 'borrows' a UIView tag property value, there is risk someone might use the same tag in their own view hierarchy, unaware of the extension's usage, creating an extremely difficult to diagnose bug. Things like that don't belong in library code or extensions. (2) It still requires the caller to declare a delegate. The Floating Placeholder avoids hacks, has a tiny footprint, is simple and entirely local, which makes it a safe bet. Commented Feb 28, 2017 at 8:24
Swift:
Add your TextView
@IBOutlet
:
@IBOutlet weak var txtViewMessage: UITextView!
In the viewWillAppear
method, do add the following :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
txtViewMessage.delegate = self // Give TextViewMessage delegate Method
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
Please add the Delegate
Using extension (UITextViewDelegate):
// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name" {
txtViewMessage.text = ""
txtViewMessage.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if txtViewMessage.text.isEmpty {
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
}
}
Here is what I'm using for getting this job done.
@IBDesignable class UIPlaceholderTextView: UITextView {
var placeholderLabel: UILabel?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshPlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
}
@IBInspectable var placeholder: String? {
didSet {
refreshPlaceholder()
}
}
@IBInspectable var placeholderColor: UIColor? = .darkGray {
didSet {
refreshPlaceholder()
}
}
@IBInspectable var placeholderFontSize: CGFloat = 14 {
didSet {
refreshPlaceholder()
}
}
func refreshPlaceholder() {
if placeholderLabel == nil {
placeholderLabel = UILabel()
let contentView = self.subviews.first ?? self
contentView.addSubview(placeholderLabel!)
placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
}
placeholderLabel?.text = placeholder
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
}
@objc func textChanged() {
if self.placeholder?.isEmpty ?? true {
return
}
UIView.animate(withDuration: 0.25) {
if self.text.isEmpty {
self.placeholderLabel?.alpha = 1.0
} else {
self.placeholderLabel?.alpha = 0.0
}
}
}
override var text: String! {
didSet {
textChanged()
}
}
}
I know there're several approaches similar to this but the benefits from this one are that it can:
- Set placeholder text, font size and color in IB.
- No longer shows the warning of "Scroll View has ambiguous scrollable content" in IB.
- Add animation to show/hide of placeholder.
-
-
-
every line
NotificationCenter.default.addObserver
must have a counter line withNotificationCenter.default.removeObserver
– fnc12Commented Sep 9, 2021 at 10:19 -
From iOS 9 and on, you don't need to explicitly remove observers: developer.apple.com/documentation/foundation/notificationcenter/…– PeiCommented Sep 9, 2021 at 10:56
-
Great solution, but I had to use
let contentView = self
or I couldn't see the placeholder as the first subview had a zero sized rect.– DarrenCommented Jan 17, 2023 at 16:24
SET value in view load
txtVw!.autocorrectionType = UITextAutocorrectionType.No
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
func textViewDidBeginEditing(textView: UITextView) {
if (txtVw?.text == "Write your Placeholder")
{
txtVw!.text = nil
txtVw!.textColor = UIColor.blackColor()
}
}
func textViewDidEndEditing(textView: UITextView) {
if txtVw!.text.isEmpty
{
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
}
textView.resignFirstResponder()
}
One more solution (Swift 3):
import UIKit
protocol PlaceholderTextViewDelegate {
func placeholderTextViewDidChangeText(_ text:String)
func placeholderTextViewDidEndEditing(_ text:String)
}
final class PlaceholderTextView: UITextView {
var notifier:PlaceholderTextViewDelegate?
var placeholder: String? {
didSet {
placeholderLabel?.text = placeholder
}
}
var placeholderColor = UIColor.lightGray
var placeholderFont = UIFont.appMainFontForSize(14.0) {
didSet {
placeholderLabel?.font = placeholderFont
}
}
fileprivate var placeholderLabel: UILabel?
// MARK: - LifeCycle
init() {
super.init(frame: CGRect.zero, textContainer: nil)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)
placeholderLabel = UILabel()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.textAlignment = .left
placeholderLabel?.numberOfLines = 0
}
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel?.font = placeholderFont
var height:CGFloat = placeholderFont.lineHeight
if let data = placeholderLabel?.text {
let expectedDefaultWidth:CGFloat = bounds.size.width
let fontSize:CGFloat = placeholderFont.pointSize
let textView = UITextView()
textView.text = data
textView.font = UIFont.appMainFontForSize(fontSize)
let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
height: CGFloat.greatestFiniteMagnitude))
let expectedTextViewHeight = sizeForTextView.height
if expectedTextViewHeight > height {
height = expectedTextViewHeight
}
}
placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)
if text.isEmpty {
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
} else {
placeholderLabel?.removeFromSuperview()
}
}
func textDidChangeHandler(notification: Notification) {
layoutSubviews()
}
}
extension PlaceholderTextView : UITextViewDelegate {
// MARK: - UITextViewDelegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
notifier?.placeholderTextViewDidChangeText(textView.text)
}
func textViewDidEndEditing(_ textView: UITextView) {
notifier?.placeholderTextViewDidEndEditing(textView.text)
}
}
result
We can implement textview PlaceHolder quite easily if we are Using pod IQKeyboardManagerSwift
in our project just have to follow 4 steps
- We have to assign the class
IQTextView
to ourTextView
class. - We have to import the
IQKeyboardManagerSwift
in our Controller page - Last but not least make the outlet of the
textView
on the Controller page // if you want :) - Give the
textView
some place holder text through the storyboard inspectable
A simple and quick solution that works for me is:
@IBDesignable
class PlaceHolderTextView: UITextView {
@IBInspectable var placeholder: String = "" {
didSet{
updatePlaceHolder()
}
}
@IBInspectable var placeholderColor: UIColor = UIColor.gray {
didSet {
updatePlaceHolder()
}
}
private var originalTextColor = UIColor.darkText
private var originalText: String = ""
private func updatePlaceHolder() {
if self.text == "" || self.text == placeholder {
self.text = placeholder
self.textColor = placeholderColor
if let color = self.textColor {
self.originalTextColor = color
}
self.originalText = ""
} else {
self.textColor = self.originalTextColor
self.originalText = self.text
}
}
override func becomeFirstResponder() -> Bool {
let result = super.becomeFirstResponder()
self.text = self.originalText
self.textColor = self.originalTextColor
return result
}
override func resignFirstResponder() -> Bool {
let result = super.resignFirstResponder()
updatePlaceHolder()
return result
}
}
Swift 3.2
extension EditProfileVC:UITextViewDelegate{
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
}
First when user start editing textViewDidBeginEditing call and then check the if colour of text grey means user didn't write anything then set as textview nil and change the colour to black for user texting.
When user end editing textViewDidEndEditing is call and check if user doesn't write anything in textview then text set as grey colour with text "PlaceHolder"
-
Looks as the shorter and the simplest answer with less of code Commented Jun 28, 2020 at 19:59
Here is my way of solving this problem (Swift 4):
The idea was to make the simplest possible solution which allows to use placeholders of different colors, resizes to placeholders size, will not overwrite a delegate
meanwhile keeping all UITextView
functions work as expected.
import UIKit
class PlaceholderTextView: UITextView {
var placeholderColor: UIColor = .lightGray
var defaultTextColor: UIColor = .black
private var isShowingPlaceholder = false {
didSet {
if isShowingPlaceholder {
text = placeholder
textColor = placeholderColor
} else {
textColor = defaultTextColor
}
}
}
var placeholder: String? {
didSet {
isShowingPlaceholder = !hasText
}
}
@objc private func textViewDidBeginEditing(notification: Notification) {
textColor = defaultTextColor
if isShowingPlaceholder { text = nil }
}
@objc private func textViewDidEndEditing(notification: Notification) {
isShowingPlaceholder = !hasText
}
// MARK: - Construction -
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
}
// MARK: - Destruction -
deinit { NotificationCenter.default.removeObserver(self) }
}
Swift Answer
Here is the custom class, that animates placeholder.
class CustomTextView: UITextView {
// MARK: - public
public var placeHolderText: String? = "Enter Reason.."
public lazy var placeHolderLabel: UILabel! = {
let placeHolderLabel = UILabel(frame: .zero)
placeHolderLabel.numberOfLines = 0
placeHolderLabel.backgroundColor = .clear
placeHolderLabel.alpha = 0.5
return placeHolderLabel
}()
// MARK: - Init
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
enableNotifications()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
enableNotifications()
}
func setup() {
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.sizeToFit()
}
// MARK: - Cycle
override func awakeFromNib() {
super.awakeFromNib()
textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 8)
returnKeyType = .done
addSubview(placeHolderLabel)
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.textColor = textColor
placeHolderLabel.font = font
placeHolderLabel.text = placeHolderText
bringSubviewToFront(placeHolderLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
// MARK: - Notifications
private func enableNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(_:)), name: UITextView.textDidChangeNotification , object: nil)
}
@objc func textDidChangeNotification(_ notify: Notification) {
guard self == notify.object as? UITextView else { return }
guard placeHolderText != nil else { return }
UIView.animate(withDuration: 0.25, animations: {
self.placeHolderLabel.alpha = (self.text.count == 0) ? 0.5 : 0
}, completion: nil)
}
}
Contrary to just about every answer on this post, UITextView
does have a placeholder property. For reasons beyond my comprehension, it is only exposed in IB, as such:
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="My Placeholder"/>
</userDefinedRuntimeAttributes>
So if you are using storyboards and a static placeholder will suffice, just set the property on the inspector.
You can also set this property in code like this:
textView.setValue("My Placeholder", forKeyPath: "placeholder")
Its cloudy as to weather this is accessed via private API, as the property is exposed.
I haven't tried submitting with this method. But I will be submitting this way shortly and will update this answer accordingly.
UPDATE:
I have shipped this code in multiple releases with no issues from Apple.
UPDATE: This will only work in Xcode pre 11.2
-
in Swift 5 you can write myTextView,placeholder = "enter your eye color" Commented Apr 19, 2019 at 10:44
-
@user462990 Can you provide a documentation link for this? I don't believe this is accurate. Commented Apr 24, 2019 at 20:09
-
sorry, didn't find dox but it's really easy to test... eg "alert.addTextField { (textField3) in textField3.placeholder = language.JBLocalize(phrase: "yourName") }. jbLocalize is a translation manager which returns a String Commented Apr 26, 2019 at 12:00
-
4@user462990 You are referencing
UITextField
NOTUITextView
please read the questions/responses more carefully. Commented May 1, 2019 at 0:22 -
3Great solution, but does not work for me. Somehow it always crashes. Could you specify of deployment target, swift and xCode versions you are using? Commented Aug 15, 2019 at 12:20
I don't know why people over complicate this issue so much.... It's fairly straight forward and simple. Here's a subclass of UITextView that provides the requested functionality.
- (void)customInit
{
self.contentMode = UIViewContentModeRedraw;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
- (void)textChanged:(NSNotification *)notification
{
if (notification.object == self) {
if(self.textStorage.length != 0 || !self.textStorage.length) {
[self setNeedsDisplay];
}
}
}
#pragma mark - Setters
- (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
{
self.placeholderText = placeholderText;
self.placeholderTextFont = font;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor lightGrayColor] setFill];
if (self.textStorage.length != 0) {
return;
}
CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
NSDictionary *attributes = @{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
[self.placeholderText drawInRect:inset withAttributes:attributes];
}`
-
FYI, before someone beats me to it... this is fairly direct to translate to Swift. Nothing complicated here. Commented Aug 11, 2015 at 21:33
This is my ready to use solution if you are working with multiple text views
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
// Set cursor to the beginning if placeholder is set
if textView.textColor == UIColor.lightGrayColor() {
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
return true
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Remove placeholder
if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
textView.text = ""
textView.textColor = UIColor.blackColor()
}
if text == "\n" {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(textView: UITextView) {
// Set placeholder if text is empty
if textView.text.isEmpty {
textView.text = NSLocalizedString("Hint", comment: "hint")
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
}
func textViewDidChangeSelection(textView: UITextView) {
// Set cursor to the beginning if placeholder is set
let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
// Do not change position recursively
if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
textView.selectedTextRange = firstPosition
}
}
Swift - I wrote a class that inherited UITextView and I added a UILabel as a subview to act as a placeholder.
import UIKit
@IBDesignable
class HintedTextView: UITextView {
@IBInspectable var hintText: String = "hintText" {
didSet{
hintLabel.text = hintText
}
}
private lazy var hintLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFontOfSize(16)
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
private func setupView() {
translatesAutoresizingMaskIntoConstraints = false
delegate = self
font = UIFont.systemFontOfSize(16)
addSubview(hintLabel)
NSLayoutConstraint.activateConstraints([
hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
hintLabel.heightAnchor.constraintEqualToConstant(30)
])
}
override func layoutSubviews() {
super.layoutSubviews()
setupView()
}
}
-
Great example of using constraints to position the placeholder, however the ideal position of the placeholder is right above where the first character of the text is going to go, so calculating that position based on the the text view's font is better for that because it adapts to whatever size of font is set for the text view. Commented Dec 10, 2016 at 0:02
Swift 3.1
This extension worked well for me: https://github.com/devxoul/UITextView-Placeholder
Here is a code snippet:
Install it via pod:
pod 'UITextView+Placeholder', '~> 1.2'
Import it to your class
import UITextView_Placeholder
And add placeholder
property to your already created UITextView
textView.placeholder = "Put some detail"
I had to dispatch queue to get my placeholder text to reappear once editing was completed.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Description" {
textView.text = nil
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
DispatchQueue.main.async {
textView.text = "Description"
}
}
}
-
What do I type instead of last line ”textView.text = ”Description” if I want this value to be what user is typing ?– ISSCommented Sep 6, 2018 at 10:56
Swift 4, 4.2 and 5
[![@IBOutlet var detailTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
detailTextView.delegate = self
}
extension ContactUsViewController : UITextViewDelegate {
public func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Write your message here..." {
detailTextView.text = ""
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.86)
}
textView.becomeFirstResponder()
}
public func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == "" {
detailTextView.text = "Write your message here..."
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.30)
}
textView.resignFirstResponder()
}
[![}][1]][1]
Swift 5.2
Standalone class
Use this if you want a class which you can use anywhere as it is self contained
import UIKit
class PlaceHolderTextView:UITextView, UITextViewDelegate{
var placeholderText = "placeholderText"
override func willMove(toSuperview newSuperview: UIView?) {
textColor = .lightText
delegate = self
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == placeholderText{
placeholderText = textView.text
textView.text = ""
textView.textColor = .darkText
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == ""{
textView.text = placeholderText
textColor = .lightText
}
}
}
The key here is the willMove(toSuperView:)
function as it allows you to setup the view before being added to another view's hierarchy (similar to viewDidLoad/viewWillAppear in ViewControllers)
SWIFTUI
Here is a Swiftui TextView made using UIVIewRepresentable that has placeholder functionality and border colors
struct TextView: UIViewRepresentable {
@Binding var text: String
var placeholderText: String
var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.delegate = context.coordinator
textView.layer.borderWidth = 0.6
textView.layer.borderColor = UIColor.lightGray.cgColor
textView.layer.cornerRadius = 10
textView.text = placeholderText
textView.textColor = UIColor.lightGray
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = self.parent.placeholderText
textView.textColor = UIColor.lightGray
}
}
}
}
then in your View you can use it like this
TextView(text: self.$viewModel.addPostCommentText, placeholderText: "Share your story about this cash", textStyle: .body)
.padding()
.frame(height: 150)
No need to add any third party library. Just use below code...
class SubmitReviewVC : UIViewController, UITextViewDelegate {
@IBOutlet var txtMessage : UITextView!
var lblPlaceHolder : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
txtMessage.delegate = self
lblPlaceHolder = UILabel()
lblPlaceHolder.text = "Enter message..."
lblPlaceHolder.font = UIFont.systemFont(ofSize: txtMessage.font!.pointSize)
lblPlaceHolder.sizeToFit()
txtMessage.addSubview(lblPlaceHolder)
lblPlaceHolder.frame.origin = CGPoint(x: 5, y: (txtMessage.font?.pointSize)! / 2)
lblPlaceHolder.textColor = UIColor.lightGray
lblPlaceHolder.isHidden = !txtMessage.text.isEmpty
}
func textViewDidChange(_ textView: UITextView) {
lblPlaceHolder.isHidden = !textView.text.isEmpty
}
}
There is no such property in ios to add placeholder directly in TextView rather you can add a label and show/hide on the change in textView. SWIFT 2.0 and make sure to implement the textviewdelegate
func textViewDidChange(TextView: UITextView)
{
if txtShortDescription.text == ""
{
self.lblShortDescription.hidden = false
}
else
{
self.lblShortDescription.hidden = true
}
}
I like @nerdist's solution. Based on that, I created an extension to UITextView
:
import Foundation
import UIKit
extension UITextView
{
private func add(_ placeholder: UILabel) {
for view in self.subviews {
if let lbl = view as? UILabel {
if lbl.text == placeholder.text {
lbl.removeFromSuperview()
}
}
}
self.addSubview(placeholder)
}
func addPlaceholder(_ placeholder: UILabel?) {
if let ph = placeholder {
ph.numberOfLines = 0 // support for multiple lines
ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
ph.sizeToFit()
self.add(ph)
ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
ph.textColor = UIColor(white: 0, alpha: 0.3)
updateVisibility(ph)
}
}
func updateVisibility(_ placeHolder: UILabel?) {
if let ph = placeHolder {
ph.isHidden = !self.text.isEmpty
}
}
}
In a ViewController class, for example, this is how I use it:
class MyViewController: UIViewController, UITextViewDelegate {
private var notePlaceholder: UILabel!
@IBOutlet weak var txtNote: UITextView!
...
// UIViewController
override func viewDidLoad() {
notePlaceholder = UILabel()
notePlaceholder.text = "title\nsubtitle\nmore..."
txtNote.addPlaceholder(notePlaceholder)
...
}
// UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
txtNote.updateVisbility(notePlaceholder)
...
}
Placeholder on UITextview!
UPDATE:
In case you change textview's text in code, remember to call updateVisibitly method to hide placeholder:
txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.
To prevent the placeholder being added more than once, a private add()
function is added in extension
.
-
Thanks again for your improvement to the original. I spent wayyy too much time on this this weekend, but I think you'll enjoy the EZ Placeholder extreme variant I eventually came up with: stackoverflow.com/a/41081244/2079103 (I gave you credit for contributing to the evolution of the placeholder solutions) Commented Dec 12, 2016 at 17:16
In swift2.2:
public class CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public var textContainerInset: UIEdgeInsets {
didSet {
updateConstraintsForPlaceholderLabel()
}
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(textDidChange),
name: UITextViewTextDidChangeNotification,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clearColor()
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .Width,
relatedBy: .Equal,
toItem: self,
attribute: .Width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
}
@objc private func textDidChange() {
placeholderLabel.hidden = !text.isEmpty
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self,
name: UITextViewTextDidChangeNotification,
object: nil)
}
}
In swift3:
import UIKit
class CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public var textContainerInset: UIEdgeInsets {
didSet {
updateConstraintsForPlaceholderLabel()
}
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clear
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
}
@objc private func textDidChange() {
placeholderLabel.isHidden = !text.isEmpty
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
}
}
I wrote a class in swift. You need to import this class whenever required.
I can't add comment because of reputation. add one more delegate need in @clearlight answer.
func textViewDidBeginEditing(_ textView: UITextView) {
cell.placeholderLabel.isHidden = !textView.text.isEmpty
}
is need
because textViewDidChange
is not called first time