Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
The correct way to implement a placeholder in a UITextView (Swift)
//
// UITextViewPlaceholder.swift
// TextViewPlaceholder
//
// Copyright (c) 2017 Tijme Gommers <tijme@finnwea.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
/// Extend UITextView and implemented UITextViewDelegate to listen for changes
extension UITextView: UITextViewDelegate {
/// Resize the placeholder when the UITextView bounds change
override open var bounds: CGRect {
didSet {
self.resizePlaceholder()
}
}
/// The UITextView placeholder text
public var placeholder: String? {
get {
var placeholderText: String?
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderText = placeholderLabel.text
}
return placeholderText
}
set {
if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
placeholderLabel.text = newValue
placeholderLabel.sizeToFit()
} else {
self.addPlaceholder(newValue!)
}
}
}
/// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
///
/// - Parameter textView: The UITextView that got updated
public func textViewDidChange(_ textView: UITextView) {
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderLabel.isHidden = self.text.characters.count > 0
}
}
/// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
private func resizePlaceholder() {
if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
let labelX = self.textContainer.lineFragmentPadding
let labelY = self.textContainerInset.top - 2
let labelWidth = self.frame.width - (labelX * 2)
let labelHeight = placeholderLabel.frame.height
placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
}
}
/// Adds a placeholder UILabel to this UITextView
private func addPlaceholder(_ placeholderText: String) {
let placeholderLabel = UILabel()
placeholderLabel.text = placeholderText
placeholderLabel.sizeToFit()
placeholderLabel.font = self.font
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.tag = 100
placeholderLabel.isHidden = self.text.characters.count > 0
self.addSubview(placeholderLabel)
self.resizePlaceholder()
self.delegate = self
}
}

Hi,
Its a great way to extend the textview to add functionality instead of subclassing.
I found a small issue with it--The call to resizePlaceholder() method(Line 76) should be made after adding the label as a subview because, the if let condition in the resizePlaceholder method fails as the label is not a subview yet and the label's frame isn't set.

Owner

tijme commented Mar 29, 2017

@nithingwl Hi, sorry for the late response, somehow I didn't get a notification.

I fixed the issue by switching those lines. Thanks!

germs5 commented Apr 6, 2017

It doesn't seem to handle multiple lines. I am seeing truncation with ellipsis at the end of the first line. Perhaps because textContainer frame is not the same as the UITextView frame?

Owner

tijme commented Apr 22, 2017

@germs5 Hmm, I didn't implement that indeed. I think there are two things you can try. Either use adjustsFontSizeToFitWidth on the placeholder label or increase the height and make it multiline.

liaa commented May 24, 2017

I think it's better not to take control of the delegate property in the extension. Because developer may need set delegate to another Object and do more stuff. We can listen to the text change this way:

 NotificationCenter.default.addObserver(self,
                                               selector: #selector(textViewDidChange),
                                               name: NSNotification.Name.UITextViewTextDidChange,
                                               object: nil)
Owner

tijme commented May 24, 2017

@liaa Great solution! I am not able to test this right now but I'll merge it as soon as I have time.

congnd commented Jun 5, 2017

what is the license of this lib?

Owner

tijme commented Jun 5, 2017 edited

@congnd MIT. I just added the license at the top of the Swift file.

what if i need delegate somewhere else?

@astrokin
@liaa 's answear with adding observer works for me

@liaa Thanks! :) I used this nice extension. but, when I use textview's delegate, then delegate not work well.. it tangled. but I understand your think, and I implement that, then It work perfectly. thanks!

Owner

tijme commented Aug 18, 2017

@seubseub You can also use @liaa 's solution 😄

yuvalt commented Sep 5, 2017

Thanks! I forked the gist and added a few more values to control (font, color, inset) with support for Interface Builder.

Owner

tijme commented Sep 5, 2017

@yuvalt Ah I see, very nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment