How can I add a placeholder in a UITextView
, similar to the one you can set for UITextField
, in Swift
?

- 2,358
- 2
- 16
- 41

- 6,983
- 5
- 16
- 18
-
This is an age old problem in iOS development with UITextView. I've written subclasses like the one mentioned here: http://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. – cjwirth Dec 26 '14 at 03:28
-
How would I use your subclass, while using swift for the project. Using a bridge file? – StevenZ Dec 26 '14 at 03: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. – cjwirth Dec 26 '14 at 03:52
-
You can use UIFloatLabelTextView sample from GitHub. This position placeholder on top while writing. Really interesting one! https://github.com/ArtSabintsev/UIFloatLabelTextView – Jayprakash Dubey Aug 01 '16 at 08: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) – TheCodingArt Dec 31 '16 at 19:09
-
@TheCodingArt - this was created to address that concern: http://stackoverflow.com/a/28271069/2079103 – clearlight Jan 31 '17 at 01:16
-
@clearlight that's still more complicated than my solution lol. It's also toggling different view states and different types of classes (a UILabel and a UITextView). – TheCodingArt Feb 01 '17 at 01:12
-
@TheCodingArt. Would love to see an example. – clearlight Feb 01 '17 at 01:14
-
@clearlight it's one of the answers below lol: http://stackoverflow.com/questions/27652227/text-view-placeholder-swift/31952339#31952339 – TheCodingArt Feb 01 '17 at 01:15
-
`UITextView` has a placeholder property, you just have to set it within IB or via Keypath. No need for subclassing unless additional logic needs to be applied to the state. https://stackoverflow.com/questions/27652227/text-view-uitextview-placeholder-swift/55661099#55661099 – Alex Chase Aug 06 '19 at 04:04
44 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)
}
}
}

- 37,080
- 10
- 92
- 128
-
@StevenR You have to delete textViewDidBeginEditing and textViewDidEndEditing – Lyndsey Scott Dec 26 '14 at 05:33
-
@LyndseyScott This really helped me too. But I don't understand why you needed to combine the textview text and replacement text into the "updatedText" variable. So messing around, I tried - `if countElements(text) == 0` - which seemed to also work. Looking at the "shouldChangeTextInRange" method, it sounds like the "text" parameter is for the replacement new text, so therefore it seems like you could test if the text value is 0 or not, instead of the updatedText variable. Is there an issue I missing with this idea? – ScottEdwards2000 Dec 27 '14 at 02:28
-
@ScottEdwards2000 That won't work. For example, if you type a string with 10 characters then hit backspace once, the replacement text will have length == 0 and the placeholder will show up even though there was still text in the text field. The only way to tell if the updated text will have a length equal to 0 is to do what I've done with updatedText and combine the text view with its replacement. – Lyndsey Scott Dec 27 '14 at 02:32
-
However, I do notice using "text" that when I hit the backspace it deletes all letters, versus with updatedText it deletes one letter at a time, which is better. Do you know why this is? – ScottEdwards2000 Dec 27 '14 at 02:35
-
@ScottEdwards2000 Yes, because just like I said, "if you type a string with 10 characters then hit backspace once, the replacement text will have length == 0 and the placeholder will show up even though there was still text in the text field." So the old text will delete and the placeholder will show if you simply do `if countElements(text) == 0`. That's why you *have* to do it using some sort of `updatedText` variable combining the old text with the replacement text in order to be able to detect backspaces appropriately. – Lyndsey Scott Dec 27 '14 at 02:37
-
when you say the "replacement text", do you mean the 3rd argument of the function? I don't see where u call the function with that parameter in your code. This is quite confusing to a newbie! Thanks!! – ScottEdwards2000 Dec 27 '14 at 03:30
-
@ScottEdwards2000 func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, *replacementText* text: String) -> Bool ... replacementText is "text". – Lyndsey Scott Dec 27 '14 at 03:31
-
@LyndseyScott I finally got it after reading your comments and stepping through with the debugger. Your solution is exactly what I was looking for and thank you so much! – ScottEdwards2000 Dec 27 '14 at 19:39
-
@LyndseyScott, well, I tried to make an edit to your post, but it was denied, so maybe a comment will be better. `countElements` is used twice in your code, this has been changed in a newer version of swift to just `count`. – TaylorAllred May 01 '15 at 00:37
-
hi, this works perfectly but how can I make the first letter capital ? Though I set the Capitalisation for Sentences in the Xcode interface builder it does not work. – Ankahathara Jun 11 '15 at 06:38
-
@Ankahathara Are you asking how to make the first letter of the UITextView text capital? Sounds like a completely different question that you should perhaps post/look for elsewhere... – Lyndsey Scott Jun 11 '15 at 09:51
-
Swift 1.2 iOs 8.4 , comparing to `nil` is not working for me, instead do a comparison to `""` , absence of text. – Juan Boero Aug 11 '15 at 20:56
-
@JuanPabloBoero Other than this line: `if self.view.window != nil` I don't see any nil comparisons in the code I wrote... Instead of comparing text with "", I used the `isEmpty` property: `if textView.text.isEmpty {`. I'd recommend using `isEmpty` over doing a comparison to "". – Lyndsey Scott Aug 11 '15 at 21:22
-
This is a pretty bad answer with far to many states you must take into consideration ...... – TheCodingArt Aug 11 '15 at 21:32
-
@TheCodingArt "with far to [sic] many states you must take into consideration" Such as...? – Lyndsey Scott Aug 11 '15 at 21:33
-
@LyndseyScott this alone is a state problem: textView.text = "Placeholder".. When you grab the placeholder text from any view that naturally supports placeholder text, you don't expect to receive the placeholder text from the text property. This means to get that functionality, you have to juggle the difference between text, attributedText, placeholderText, etc. – TheCodingArt Aug 11 '15 at 21:35
-
Using the text property alone and juggling those states causes many many unexpected headaches down the line. – TheCodingArt Aug 11 '15 at 21:36
-
@TheCodingArt "When you grab the placeholder text from any view that naturally supports placeholder text..." I think you're misunderstanding the point of this question/answer. `UITextView`'s don't have a placeholder property, thus the need for this workaround. – Lyndsey Scott Aug 11 '15 at 21:36
-
@LyndseyScott I do understand the point... and I have provided an answer. I'm pointing out major flaws with this answer and why it's not a good way to go about it. – TheCodingArt Aug 11 '15 at 21:37
-
I tried adding placeholder text to multiple textviews and giving each their own tag but that didn't work. They all reset to the first one's initial text. Any ideas? – Aug 14 '15 at 18:43
-
@theActuary Sounds like a code issue so I'd recommend posting your question on Stack Overflow. – Lyndsey Scott Aug 14 '15 at 18:44
-
I don't know if you mention this before but: Is necessary to put UITextViewDelegate and do delegate from your textView to your view. – Dasoga Sep 15 '15 at 22:27
-
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, the `textViewDidBeginEditing` and the `textViewDidEndEditing` functions will not work. – Ujjwal-Nadhani Apr 23 '16 at 17:05
-
That's mess, you use color as your model. It's in opposite to all design rules. Please hold metadata in other place. Tag or something. – Leszek Zarna Jun 26 '17 at 08:20
-
2The code is not compiling, I am having an error as `Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range
' (aka 'Range – iPeter Jun 27 '17 at 15:02')`. -
-
-
@iPeter try this: `let updatedText = currentText?.replacingCharacters(in: Range(range, in: currentText!)!, with: text)` – user3647894 Jul 14 '17 at 16:28
-
I am having the same issue as @iPeter in Swift 3. Won't compile and gives that error on the `let updatedText =` line. – Baylor Mitchell Jul 14 '17 at 22:04
-
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 the `let updatedText =` line to `let updatedText = currentText?.replacingCharacters(in: range, with: text)`. Finally, transform the `if updatedText.isEmpty` line to `if (updatedText?.isEmpty)! {`. That should do the trick! – Baylor Mitchell Jul 14 '17 at 22:21
-
Help... When I paste some text to textview, it change placeholder to my pasted text, but text color still gray although textView.textColor = UIColor.black called – Tà Truhoada May 07 '18 at 10:47
-
1@TàTruhoada I’ve updated the solution to handle copy and paste scenarios – Lyndsey Scott May 07 '18 at 15:34
-
@LyndseyScott else if textView.textColor == UIColor.lightGray && !text.isEmpty { textView.textColor = UIColor.black textView.text = text } if I return false, the first charactor will not show – Tà Truhoada May 08 '18 at 03:45
-
@TàTruhoada Are you sure you’ve entered all the code as written? Including the else { return true } block? – Lyndsey Scott May 08 '18 at 05:43
-
@TàTruhoada It personally works for me. If you’d like to post a link to your code, I can take a look. – Lyndsey Scott May 08 '18 at 14:49
-
@LyndseyScott Thank you! but I can't share my project. I will try to solve my problem. Thanks for your support! – Tà Truhoada May 09 '18 at 03:18
-
This does not behave like a UITextField, whereby the placeholder dissapears when text is entered (not when editing begins) – agandi May 18 '18 at 14:40
-
-
1@LyndseyScott After a long time, today I come back to fix my problem. And then I realize my code is still holding an old line in your code. Instead of using "textView.text = text", I used "textView.text = nil", so it's not working. After edited, it works like a charm! Thank you so much! – Tà Truhoada May 29 '18 at 10:09
-
@LyndseyScott a silly question, since I am not native English speaker, what is the reason of not using period in comment? I googled but can not find a useful information. Thank you. – Houcheng Sep 17 '18 at 01:46
-
@LyndseyScott I'm also an iOS developer at BT (the best company in the world) let's go out. PM me – User123335511231 Jan 14 '19 at 15:03
-
3@LyndseyScott setting `textView.selectedTextRange` from within `func textViewDidChangeSelection(_ textView: UITextView)` causes an infinite loop... – MikeG Feb 05 '19 at 18:37
-
`UITextView` _does_ have a `placeholder` property. It can be set in IB or via Keypath – Alex Chase Aug 06 '19 at 04:06
-
How to use your answer to allow specific length of text in the `UITextView`? @LyndseyScott – Hemang Aug 08 '19 at 07:53
-
For the first time, cursor is not moving to beginning of placeholder, so i put the cursor mover code i.e. in "textViewDidChangeSelection" delegate to also add in "textViewShouldBeginEditing" and now it's working perfectly. – g212gs Jun 09 '20 at 07:09
-
At least atm, `UITextView.text` is implicitly unwrapped (`!`), so for the first solution, the property should not be set to `nil`, rather `""` – Cloud Aug 11 '20 at 17:13
-
30It'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. – Kenny Aug 31 '20 at 13:01
-
This solution is very simple and seems bug free while it is hardly reusable you need to write those code every time you use this feature – Bigair Oct 05 '20 at 12:17
-
I get a warning with "Snapshotting a view ... requires afterScreenUpdates:YES". It's caused by setting a text in `textViewDidBeginEditing`. To get rid of this warning, move the text and color setting to `textViewShouldBeginEditing` and return true – boweidmann Jan 31 '21 at 11:55
-
I can't cut text of textView in second solution :( It is just deleted but not copied. – Flatout Mar 29 '21 at 15:16
-
1On iOS 15, when I select a text from keyboard suggestion, it gets written in the textView 2 times. How to solve this? @LyndseyScott – Raj D Oct 21 '21 at 14:23
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
}
}

- 12,255
- 11
- 57
- 75
-
32This method is undeniably the simplest and least error-prone I've found. Fabulous. – David Feb 03 '15 at 02:17
-
6It is a good method but label text may go out of bound if placeholder text is very long.. – Aks Feb 03 '15 at 07: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? – Tommie C. Nov 04 '15 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. – blwinters Jun 21 '17 at 15:31
-
@clearlight In my case, I have a text view for entering the name of an object at the top of a grouped tableview, centered between two buttons. (Similar to the Trello app, but centered.) It starts with one line and increases its height as new lines are needed, using `UITableViewAutomaticDimension`, `beginUpdates()`, `endUpdates()`. When the text is empty and the placeholder is visible (single line), I want it to look and behave more like a center aligned `UITextField`, which I'm pretty sure places the cursor to the left of a centered placeholder. – blwinters Jun 27 '17 at 00:28
-
-
This is a much more elegant and reliable solution! Definitely suggest this one. – msweet168 Nov 11 '21 at 08:43
-
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 }` – Rakshitha Muranga Rodrigo Feb 10 '23 at 03:37
-
when select all text,it crash ```[XXXController selectAll:]: unrecognized selector sent to instance 0x14e821800' terminating with uncaught exception of type NSException``` – iHTCboy Feb 19 '23 at 10:39
-
@IHTCboy I don't see how that crash could be in any way related to this answer, as this is very straightforward basic UIKit stuff unlikely to product side-effects if your class is coded properly, and you don't have any dangling or undefined references. I think something else is going on in your app that's making it fragile. – clearlight Feb 22 '23 at 04:58
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
.

- 6,281
- 1
- 44
- 62
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
.

- 928
- 11
- 14
-
1
-
`textView.textStorage.delegate = self` this in a view-controller will require us to bind that view-controller with `NSTextStorageDelegate`. It is really requires? – Hemang Aug 08 '19 at 08:21
-
1
-
1@Hemang it's the text view itself being `NSTextStorageDelegate`, not the view controller. – yesleon Sep 19 '19 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
}
}

- 3,321
- 2
- 27
- 52
-
I tried to use in tableview cell. When I set textView text then it does not gets set as showingPlaceholder is true by default. – Nitesh Mar 31 '21 at 07: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
}
}

- 1,987
- 2
- 16
- 32
-
1This way is a bit hacky but it's far and away the easiest and generates exactly the results you want. Good idea – William T. Jun 11 '17 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](http://stackoverflow.com/a/28271069/2079103) avoids hacks, has a tiny footprint, is simple and entirely local, which makes it a safe bet. – clearlight Feb 28 '17 at 08:24
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.

- 11,454
- 9
- 54
- 64

- 11,452
- 5
- 41
- 45
-
-
-
every line `NotificationCenter.default.addObserver` must have a counter line with `NotificationCenter.default.removeObserver` – fnc12 Sep 09 '21 at 10:19
-
From iOS 9 and on, you don't need to explicitly remove observers: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver – Pei Sep 09 '21 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. – Darren Jan 17 '23 at 16: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
}
}
}

- 417
- 4
- 15
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()
}

- 75
- 1
- 3
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

- 10,908
- 11
- 91
- 124
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

- 3,677
- 1
- 23
- 31

- 71
- 1
- 3
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
}
}

- 81
- 2
- 8
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"

- 1,179
- 1
- 10
- 23
-
Looks as the shorter and the simplest answer with less of code – Roman Romanenko Jun 28 '20 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) }
}

- 3,007
- 1
- 13
- 30
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)
}
}

- 1
- 1

- 15,485
- 6
- 64
- 84
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];
}`

- 3,436
- 4
- 30
- 53
-
FYI, before someone beats me to it... this is fairly direct to translate to Swift. Nothing complicated here. – TheCodingArt Aug 11 '15 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
}
}

- 79
- 1
- 7
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()
}
}

- 61
- 6
-
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. – clearlight Dec 10 '16 at 00: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"

- 12,848
- 3
- 65
- 75
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"
}
}
}

- 2,115
- 4
- 25
- 43
-
What do I type instead of last line ”textView.text = ”Description” if I want this value to be what user is typing ? – ISS Sep 06 '18 at 10:56
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

- 960
- 1
- 7
- 11
-
in Swift 5 you can write myTextView,placeholder = "enter your eye color" – user462990 Apr 19 '19 at 10:44
-
@user462990 Can you provide a documentation link for this? I don't believe this is accurate. – Alex Chase Apr 24 '19 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 – user462990 Apr 26 '19 at 12:00
-
4@user462990 You are referencing `UITextField` NOT `UITextView` please read the questions/responses more carefully. – Alex Chase May 01 '19 at 00:22
-
@AlexChase Have you submitted your app and was it approved with the above solution. – George Jul 29 '19 at 08:46
-
@George yes! Sorry I forgot to report back. I have been using this code in production with no issues. – Alex Chase Jul 31 '19 at 19:39
-
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? – T. Pasichnyk Aug 15 '19 at 12:20
-
@T.Pasichnyk so sorry to hear that. I'm not sure which version of UIKit this property was added in. It _should_ work if you can see the placeholder property on a textview within interfacebuilder. I am using Xcode 10.2.1 targeting iOS 11 – Alex Chase Aug 19 '19 at 03:51
-
In Xcode 11.2 – `*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key placeholder.'` – livingtech Nov 11 '19 at 18:38 -
@livingtech not surprised by this. Xcode 11.2 fixed a breaking error with UITextviews instantiated from a Xib. It could have broken this. I don't have Xcode 11.2 on my machine yet. Double check to see if placeholder text is still settable in IB on Xcode 11.2. I'll update my answer to exclude 11.2. Thanks for the info. – Alex Chase Nov 12 '19 at 00: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]

- 2,215
- 19
- 27
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)

- 732,580
- 175
- 1,330
- 1,459

- 446
- 6
- 8
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
}
}

- 374
- 5
- 9
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
}
}

- 2,832
- 4
- 23
- 51
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
.

- 37,408
- 63
- 148
- 190
-
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: http://stackoverflow.com/a/41081244/2079103 *(I gave you credit for contributing to the evolution of the placeholder solutions)* – clearlight Dec 12 '16 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.

- 1,141
- 8
- 20
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

- 448
- 4
- 11
no there is not any placeholder available for textview. you have to put label above it when user enter in textview then hide it or set by default value when user enters remove all values.
func setPlaceholder(){
var placeholderLabel = UILabel()
placeholderLabel.text = "Describe your need..."
placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0)
placeholderLabel.sizeToFit()
descriptionTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
}
//Delegate Method.
func textViewDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textView.text.isEmpty
}

- 187
- 2
- 8
Another solution could be to use keyboardWillHide and keyboardWillShow notifications, as I did.
First you need to handle listening, and unlistening to the notifications in the viewWillAppear
and viewWillAppear
methods respectively (to handle memory leaks).
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupKeyboardNotificationListeners(enable: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
setupKeyboardNotificationListeners(enable: false)
}
Then the method to handle listening/unlistening to the notifications:
private func setupKeyboardNotificationListeners(enable: Bool) {
if enable {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
} else {
NotificationCenter.default.removeObserver(self)
}
}
Then in the both methods for keyboardWillHide and keyboardWillShow you handle the placeholder and color changes of the text.
@objc func keyboardWillShow(notification: NSNotification) {
if self.textView.text == self.placeholder {
self.textView.text = ""
self.textView.textColor = .black
}
}
@objc func keyboardWillHide(notification: NSNotification) {
if self.textView.text.isEmpty {
self.textView.text = self.placeholder
self.textView.textColor = .lightGrey
}
}
I found this solution to be the best one so far since the text will be removed as soon as the keyboard appears instead of when the user starts typing, which can cause confusion.

- 1,354
- 2
- 18
- 30
I believe this is a very clean solution. It adds a dummy text view underneath the actual text view and shows or hides it depending on the text in the actual text view:
import Foundation
import UIKit
class TextViewWithPlaceholder: UITextView {
private var placeholderTextView: UITextView = UITextView()
var placeholder: String? {
didSet {
placeholderTextView.text = placeholder
}
}
override var text: String! {
didSet {
placeholderTextView.isHidden = text.isEmpty == false
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
applyCommonTextViewAttributes(to: self)
configureMainTextView()
addPlaceholderTextView()
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: UITextView.textDidChangeNotification,
object: nil)
}
func addPlaceholderTextView() {
applyCommonTextViewAttributes(to: placeholderTextView)
configurePlaceholderTextView()
insertSubview(placeholderTextView, at: 0)
}
private func applyCommonTextViewAttributes(to textView: UITextView) {
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textContainer.lineFragmentPadding = 0
textView.textContainerInset = UIEdgeInsets(top: 10,
left: 10,
bottom: 10,
right: 10)
}
private func configureMainTextView() {
// Do any configuration of the actual text view here
}
private func configurePlaceholderTextView() {
placeholderTextView.text = placeholder
placeholderTextView.font = font
placeholderTextView.textColor = UIColor.lightGray
placeholderTextView.frame = bounds
placeholderTextView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
override func layoutSubviews() {
super.layoutSubviews()
placeholderTextView.frame = bounds
}
@objc func textDidChange() {
placeholderTextView.isHidden = !text.isEmpty
}
}

- 1,868
- 1
- 16
- 42
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)

- 770
- 8
- 15
Protocol version of clearlight's answer above, because protocols are great. Pop in it where ever you please. Dunk!
extension UITextViewPlaceholder where Self: UIViewController {
// Use this in ViewController's ViewDidLoad method.
func addPlaceholder(text: String, toTextView: UITextView, font: UIFont? = nil) {
placeholderLabel = UILabel()
placeholderLabel.text = text
placeholderLabel.font = font ?? UIFont.italicSystemFont(ofSize: (toTextView.font?.pointSize)!)
placeholderLabel.sizeToFit()
toTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (toTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !toTextView.text.isEmpty
}
// Use this function in the ViewController's textViewDidChange delegate method.
func textViewWithPlaceholderDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textView.text.isEmpty
}
}

- 2,820
- 1
- 19
- 14
TEXT VIEW DELEGATE METHODS
Use these two delegate methods and also write UITextViewDelegate in your class
func textViewDidBeginEditing(_ textView: UITextView) {
if (commentsTextView.text == "Type Your Comments")
{
commentsTextView.text = nil
commentsTextView.textColor = UIColor.darkGray
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if commentsTextView.text.isEmpty
{
commentsTextView.text = "Type Your Comments"
commentsTextView.textColor = UIColor.darkGray
}
textView.resignFirstResponder()
}

- 1
- 1

- 1,751
- 20
- 23
Here's something that can be dropped into a UIStackView
, it will size itself using an internal height constraint. Tweaking may be required to suit specific requirements.
import UIKit
public protocol PlaceholderTextViewDelegate: class {
func placeholderTextViewTextChanged(_ textView: PlaceholderTextView, text: String)
}
public class PlaceholderTextView: UIView {
public weak var delegate: PlaceholderTextViewDelegate?
private var heightConstraint: NSLayoutConstraint?
public override init(frame: CGRect) {
self.allowsNewLines = true
super.init(frame: frame)
self.heightConstraint = self.heightAnchor.constraint(equalToConstant: 0)
self.heightConstraint?.isActive = true
self.addSubview(self.placeholderTextView)
self.addSubview(self.textView)
self.pinToCorners(self.placeholderTextView)
self.pinToCorners(self.textView)
self.updateHeight()
}
public override func didMoveToSuperview() {
super.didMoveToSuperview()
self.updateHeight()
}
private func pinToCorners(_ view: UIView) {
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
view.topAnchor.constraint(equalTo: self.topAnchor),
view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
// Accessors
public var text: String? {
didSet {
self.textView.text = text
self.textViewDidChange(self.textView)
self.updateHeight()
}
}
public var textColor: UIColor? {
didSet {
self.textView.textColor = textColor
self.updateHeight()
}
}
public var font: UIFont? {
didSet {
self.textView.font = font
self.placeholderTextView.font = font
self.updateHeight()
}
}
public override var tintColor: UIColor? {
didSet {
self.textView.tintColor = tintColor
self.placeholderTextView.tintColor = tintColor
}
}
public var placeholderText: String? {
didSet {
self.placeholderTextView.text = placeholderText
self.updateHeight()
}
}
public var placeholderTextColor: UIColor? {
didSet {
self.placeholderTextView.textColor = placeholderTextColor
self.updateHeight()
}
}
public var allowsNewLines: Bool
public required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var textView: UITextView = self.newTextView()
private lazy var placeholderTextView: UITextView = self.newTextView()
private func newTextView() -> UITextView {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
textView.delegate = self
textView.backgroundColor = .clear
return textView
}
private func updateHeight() {
let maxSize = CGSize(width: self.frame.size.width, height: .greatestFiniteMagnitude)
let textViewSize = self.textView.sizeThatFits(maxSize)
let placeholderSize = self.placeholderTextView.sizeThatFits(maxSize)
let maxHeight = ceil(CGFloat.maximum(textViewSize.height, placeholderSize.height))
self.heightConstraint?.constant = maxHeight
}
}
extension PlaceholderTextView: UITextViewDelegate {
public func textViewDidChangeSelection(_: UITextView) {
self.placeholderTextView.alpha = self.textView.text.isEmpty ? 1 : 0
self.updateHeight()
}
public func textViewDidChange(_: UITextView) {
self.delegate?.placeholderTextViewTextChanged(self, text: self.textView.text)
}
public func textView(_: UITextView, shouldChangeTextIn _: NSRange,
replacementText text: String) -> Bool {
let containsNewLines = text.rangeOfCharacter(from: .newlines)?.isEmpty == .some(false)
guard !containsNewLines || self.allowsNewLines else { return false }
return true
}
}

- 1,226
- 14
- 16
var placeholderLabel : UILabel!
textviewDescription.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Add a description"
func textViewDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textviewDescription.text.isEmpty
}

- 257
- 2
- 4
-
Textview placeholder to use only label for placeholder in textview and hidden the placeholder to enter the text in textview – Srinivasan.M Sep 19 '18 at 07:46
Our solution avoids mucking with the UITextView
text
and textColor
properties, which is handy if you're maintaining a character counter.
It's simple:
1) Create a dummy UITextView
in Storyboard with the same properties as the master UITextView
. Assign placeholder text to the dummy text.
2) Align the top, left, and right edges of the two UITextViews.
3) Place the dummy behind the master.
4) Override the textViewDidChange(textView:)
delegate function of the master, and show the dummy if the master has 0 characters. Otherwise, show the master.
This assumes both UITextViews
have transparent backgrounds. If they do not, place the dummy on top when there are 0 characters, and push it underneath when there are > 0 characters. You will also have to swap responders to make sure the cursor follows the right UITextView
.

- 33,605
- 61
- 269
- 439
there is my simple version of UITextView
with placeholder. The main idea is:
- hide placeholder if user starts editing and placeholder is visible
- show placeholder if user ends editing and
text
of text view is empty.
class PlaceholderTextView: UITextView {
var placeholder = "" {
didSet {
if isPlaceholderVisible {
showPlaceholder()
}
}
}
var isPlaceholderVisible = true {
didSet {
isPlaceholderVisible ? showPlaceholder() : hidePlaceholder()
}
}
init() {
super.init(frame: .zero, textContainer: nil)
delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func showPlaceholder() {
text = placeholder
// Set other things like color of text for placeholder, ...
}
private func hidePlaceholder() {
text = ""
// Set other things like color of text for normal input, ...
}
}
extension PlaceholderTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if isPlaceholderVisible {
isPlaceholderVisible = false
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if text.isEmpty {
isPlaceholderVisible = true
}
}
}

- 10,580
- 2
- 22
- 40
This is what I did. Leaning towards code clarity and simplicity. I needed to add a textView that will get some additional notes on my app. This additional notes can be created or amended after being saved. See below. HTH. :)
class NotesTextView: UITextView {
var placeholder = "" {
didSet {
showPlaceholder()
}
}
// if the text is the placeholder, then assign a color fitting for a
// placeholder text, else, assign it your color of choice.
override var text: String! {
didSet {
textColor = text == placeholder ? .tertiaryLabel : .systemBlue
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
delegate = self
//config your font and translateAutoResizingMaskIntoConstraints here
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func showPlaceholder() {
text = placeholder
}
private func hidePlaceholder() {
text = ""
}
}
extension NotesTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if text == placeholder {
hidePlaceholder()
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if text.isEmpty {
showPlaceholder()
}
}
}

- 656
- 6
- 12
import UIKit
import RxSwift
@IBDesignable class TextViewWithPlaceholder: UITextView {
//MARK: - Propertise
@IBInspectable var placeholderText: String = ""
let placeholderLabel = LocalizedUILabel()
private let hidePlaceholderObserver = PublishSubject<Bool>()
let disposeBag = DisposeBag()
//MARK: - Did Move To Window
override func didMoveToWindow() {
super.didMoveToWindow()
observeOnTextViewEditing()
configurePlaceholder()
}
//MARK: - Observe On Text View Editing
private func observeOnTextViewEditing() {
rx.text.subscribe(onNext: { [weak self] selectedText in
guard let self = self else { return }
self.hidePlaceholderObserver.onNext((selectedText?.isEmpty ?? true) ? false : true)
}).disposed(by: disposeBag)
}
//MARK: - Observe On Show Hide Placeholder
private func configurePlaceholder() {
hidePlaceholderObserver
.bind(to: placeholderLabel.rx.isHidden)
.disposed(by: disposeBag)
placeholderLabel.text = placeholderText
placeholderLabel.font = UIFont(name: "Poppins-Semibold", size: 16) ?? UIFont()
placeholderLabel.textColor = .lightGray
placeholderLabel.sizeToFit()
placeholderLabel.frame.origin = CGPoint(x: 8, y: 8)
addSubview(placeholderLabel)
}
}

- 5,872
- 1
- 14
- 39

- 1
- 1
- 1
Probably the most simple out-of-the-box solution for UITextView
placeholder implementation that does not suffer from:
- using
UILabel
instead ofUITextView
that might perform differently - switching to and from placeholder 'UITextView' copy that would capture first typed character that will miss from main
UITextView
control - messing with main UITextView controls
text
content replacing placeholder with empty string or first typed character. Border case is that if user enters placeholder text, some proposed implementation will treat it as a placeholder itself.
Swift 5:
import UIKit
import SnapKit
import RxSwift
import RxCocoa
class TextAreaView: UIView {
let textArea = UITextView()
let textAreaPlaceholder = UITextView()
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonSetup()
}
private func commonSetup() {
addSubview(textAreaPlaceholder)
addSubview(textArea)
textArea.isScrollEnabled = false
textArea.delegate = self
textAreaPlaceholder.isScrollEnabled = false
textAreaPlaceholder.textColor = UIColor.lightGray
textArea.snp.makeConstraints { make in
make.top.bottom.leading.trailing.equalToSuperview()
}
textAreaPlaceholder.snp.makeConstraints { make in
make.top.bottom.leading.trailing.equalTo(textArea.snp.top)
}
textAreaPlaceholder.text = "Placeholder"
updatePlaceholder()
}
func updatePlaceholder() {
if textArea.text.count > 0 {
textArea.alpha = 1.0
} else {
textArea.alpha = 0.0
}
}
}
extension TextAreaView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
updatePlaceholder()
}
}

- 2,961
- 1
- 28
- 25
I know this is a an old question but wanted to share what I thought was a useful way of extending UITextView to have placeholderText and placeholderColor fields. Basically you cast the UITextView into a UITextField and then set the attributedPlaceholder field. PlaceholderText and placeholderColor are IBInspectable fields, so their values can be set in IB and behaves exactly as the UITextField placeholder functionality.
UITextView+Extend.h
#import <UIKit/UIKit.h>
@interface UITextView (Extend)
@property (nonatomic) IBInspectable NSString *placeholderText;
@property (nonatomic) IBInspectable UIColor *placeholderColor;
@end
UITextView+Extend.m
#import "UITextView+Extend.h"
#import "objc/runtime.h"
@implementation UITextView (Extend)
- (void)setPlaceholderText:(NSString *)placeholderText
{
objc_setAssociatedObject(self, @selector(placeholderText), placeholderText, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updatePlaceholderText];
}
- (NSString*)placeholderText
{
return objc_getAssociatedObject(self, @selector(placeholderText));
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
objc_setAssociatedObject(self, @selector(placeholderColor), placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updatePlaceholderText];
}
- (UIColor*)placeholderColor
{
return objc_getAssociatedObject(self, @selector(placeholderColor));
}
- (void)updatePlaceholderText
{
NSString *text = self.placeholderText;
UIColor *color = self.placeholderColor;
if(text && color)
{
UITextField *textField = (UITextField*)self;
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:text attributes:@{NSForegroundColorAttributeName:color}];
}
}
@end

- 522
- 1
- 5
- 11