I spent a lot of time trying to fix this simple issues but don’t managed it. I want in UITextView to use a placeholder: "translation" and when tapping the UITextView the placeholder should stay there until a character is entered but I need the cursor to be placed at the beginning of the placeholder just before "translation" word but it stays at the end of my placeholder. I find several answers and tried to do it as following but doesn’t work. The placeholder changes correctly to black colour but the 2nd command -> textView.selectedRange = NSMakeRange(0, 0) is not moving the cursor to the beginning, why?
- I replaced this NSMakeRange line with following (other suggestion I read but same result, doesn’t work) -> textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
Here the full code:
class MainViewController: UIViewController, UITextViewDelegate, UITextFieldDelegate {
@IBOutlet weak var definitionField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// setting placeholder
definitionField.textColor = .gray
definitionField.text = "translation"
definitionField.delegate = self }
func textViewDidBeginEditing(_ textView: UITextView) {
if (textView.text == "translation") {
textView.textColor = .black
textView.selectedRange = NSMakeRange(0, 0)
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if (textView.text == "") {
textView.text = "translation"
textView.textColor = .gray
}
textView.resignFirstResponder()
}
2
Answers
It appears that
selectedTextRange
is being set automatically by the system, aftertextViewDidBeginEditing
, andbecomeFirstResponder
is called. You can observe this by using a customUITextView
subclass, and overriding:You will see that this first gets set to
(0, 0)
because of your code, and thendidSet
is "magically" called again, settingselectedTextRange
to(11, 0)
.I’m not sure which method did the call. There might not even be a overridable method that gets called after that.
A simple solution would be to just use
DispatchQueue.main.async
to wait for whatever is setting the selected text range to the end, and then change the selection.The cursor will appear at the end for a brief moment, before going to the start. This seems to be only visual. When I tried to enter text when the cursor is at the end, the text is inserted before "translation".
That said, your implementation of this placeholder seems to have rather different from how one would expect a placeholder to work (like the one in
UITextField
), especially whentextViewDidEndEditing
.If you want behaviours similar to
UITextField
, or if you want to see other approaches, check out this post.The simplest solution here is to add a label that holds the placeholder. Then set its
isHidden
property based on whether there is any text in the text view.Here is a working example but it uses RxSwift to track text, color, and font changes.
What it does: