// | |
// 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.isEmpty | |
} | |
} | |
/// 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.isEmpty | |
self.addSubview(placeholderLabel) | |
self.resizePlaceholder() | |
self.delegate = self | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
@nithingwl Hi, sorry for the late response, somehow I didn't get a notification. I fixed the issue by switching those lines. Thanks! |
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
@germs5 Hmm, I didn't implement that indeed. I think there are two things you can try. Either use |
This comment has been minimized.
This comment has been minimized.
I think it's better not to take control of the NotificationCenter.default.addObserver(self,
selector: #selector(textViewDidChange),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil) |
This comment has been minimized.
This comment has been minimized.
@liaa Great solution! I am not able to test this right now but I'll merge it as soon as I have time. |
This comment has been minimized.
This comment has been minimized.
what is the license of this lib? |
This comment has been minimized.
This comment has been minimized.
@congnd MIT. I just added the license at the top of the Swift file. |
This comment has been minimized.
This comment has been minimized.
what if i need delegate somewhere else? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@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! |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Thanks! I forked the gist and added a few more values to control (font, color, inset) with support for Interface Builder. |
This comment has been minimized.
This comment has been minimized.
@yuvalt Ah I see, very nice! |
This comment has been minimized.
This comment has been minimized.
Very helpful! Thanks!
|
This comment has been minimized.
This comment has been minimized.
@manolosavi Thanks! I think you're not right about the bug though. I checked the documentation and it seems like the argument is a UITextView. |
This comment has been minimized.
This comment has been minimized.
@tijme sorry for the late reply, not sure why I never got a notification. That's odd, not sure what's going on because in my tests what I was getting in there was an edit: nevermind I just saw a difference in your code vs mine, I copied from a different fork that uses the notification style…my bad! |
This comment has been minimized.
This comment has been minimized.
@manolosavi For the notification version (which, in my view, is an important improvement over hijacking the delegate), it would be better to set the last argument to |
This comment has been minimized.
This comment has been minimized.
@manolosavi Can u please give me a link to that gist which uses NSNotification? I want to know where the observer has been removed. |
This comment has been minimized.
This comment has been minimized.
@tijme Thanks for your great extension then how can i remove placeholder at adding text programatically. for instance, mTextView.text = "Ok but i can't remove placeholder :( ". |
This comment has been minimized.
This comment has been minimized.
You can use |
This comment has been minimized.
This comment has been minimized.
"self.text.characters.count" is deprecated in Swift 4.2 and in the future may be disabled in turn. Xcode suggests changing "characters" to "string" or "substring" but it does not work. I was able to silence the warning by removing the "characters" without replacing with anything as can be seen in the attached example. P.S .: Thanks for share this code, it helped me a lot. |
This comment has been minimized.
This comment has been minimized.
Thanks for this. Two comments:
|
This comment has been minimized.
This comment has been minimized.
If you enter text programmatically then placeholder text not removed. I did following changes to make it work:
When we enter text programmatically then Note: I removed |
This comment has been minimized.
This comment has been minimized.
@germs5 If you want to support multiline placeholder text then you just need to add below line of code in
|
This comment has been minimized.
This comment has been minimized.
A small improvement to take insets into consideration:
https://gist.github.com/tijme/14ec04ef6a175a70dd5a759e7ff0b938#file-uitextviewplaceholder-swift-L70 |
This comment has been minimized.
This comment has been minimized.
Hy! Thx for the gist. |
This comment has been minimized.
This comment has been minimized.
Hi, I am new to this so please be nice lol but how do you actually use this??? |
This comment has been minimized.
This comment has been minimized.
Yeah, it should work with my changes |
This comment has been minimized.
This comment has been minimized.
You can place this file in your project and then set do |
This comment has been minimized.
This comment has been minimized.
Just wanted to say thank you for such a simple solution. |
This comment has been minimized.
This comment has been minimized.
Thanks @MoNTE48! |
This comment has been minimized.
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.