我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
当前回答
还有一个简单的答案,使用CATextLayer。
添加CATextLayer到UITextView的层。 使用UITextViewDelegate方法,简单地改变CATextLayer的颜色。
func txtViewPlaceholder() {
let textlayer = CATextLayer()
textlayer.frame = CGRect(x: 5, y: 5, width: 200, height: 18)
textlayer.contentsScale = UIScreen.main.scale
textlayer.fontSize = 12
textlayer.alignmentMode = kCAAlignmentLeft
textlayer.string = "Enter here"
textlayer.isWrapped = true
textlayer.name = "placeholder"
textlayer.backgroundColor = UIColor.white.cgColor
textlayer.foregroundColor = UIColor.black.cgColor
yourTxtVw.layer.insertSublayer(textlayer, at: 0)
}
func removeAddPlaceholder(remove: Bool, textView: UITextView) {
for layers in textView.layer.sublayers! where layers.name == "placeholder" {
if remove {
(layers as! CATextLayer).foregroundColor = UIColor.white.cgColor
} else {
(layers as! CATextLayer).foregroundColor = UIColor.black.cgColor
}
}
}
extension YourViewController : UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
removeAddPlaceholder(remove: true, textView: textView)
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.count <= 0 {
removeAddPlaceholder(remove: false, textView: textView)
}
}
}
其他回答
After looking through (and trying out) most of the proposed solutions to this seemingly obvious - but missing - feature of UITextView, the 'best' closest I found was that from BobDickinson. But I didnt like having to resort to a whole new subclass [I prefer drop-in categories for such simple functional additions], nor that it intercepted UITextViewDelegate methods, which is probably going to mess up your existing UITextView handling code. So here's my take on a drop-in category that'll work on any existing UITextView instance...
#import <objc/runtime.h>
// Private subclass needed to override placeholderRectForBounds: to correctly position placeholder
@interface _TextField : UITextField
@property UIEdgeInsets insets;
@end
@implementation _TextField
- (CGRect)placeholderRectForBounds:(CGRect)bounds
{
CGRect rect = [super placeholderRectForBounds:bounds];
return UIEdgeInsetsInsetRect(rect, _insets);
}
@end
@implementation UITextView (Placeholder)
static const void *KEY;
- (void)setPlaceholder:(NSString *)placeholder
{
_TextField *textField = objc_getAssociatedObject(self, &KEY);
if (!textField) {
textField = [_TextField.alloc initWithFrame:self.bounds];
textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
textField.userInteractionEnabled = NO;
textField.font = self.font;
textField.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
textField.insets = UIEdgeInsetsMake(self.textContainerInset.top,
self.textContainerInset.left + self.textContainer.lineFragmentPadding,
self.textContainerInset.bottom,
self.textContainerInset.right);
[self addSubview:textField];
[self sendSubviewToBack:textField];
objc_setAssociatedObject(self, &KEY, textField, OBJC_ASSOCIATION_RETAIN);
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(updatePlaceholder:) name:UITextViewTextDidChangeNotification object:nil];
}
textField.placeholder = placeholder;
}
- (NSString*)placeholder
{
UITextField *textField = objc_getAssociatedObject(self, &KEY);
return textField.placeholder;
}
- (void)updatePlaceholder:(NSNotification *)notification
{
UITextField *textField = objc_getAssociatedObject(self, &KEY);
textField.font = self.font;
[textField setAlpha:self.text.length? 0 : 1];
}
@end
它使用简单,只是显而易见的
UITextView *myTextView = UITextView.new;
...
myTextView.placeholder = @"enter text here";
它通过添加一个UITextField -在正确的位置-在你的UITextView后面,并利用它的占位符代替(因此你不必担心得到正确的颜色等),然后监听通知,每当你的UITextView被改变显示/隐藏这个UITextField(因此它不会干扰你现有的UITextViewDelegate调用)。这里面没有什么神奇的数字……: -)
objc_setAssociatedObject()/objc_getAssociatedObject()是为了避免必须子类化UITextView。[不幸的是,要正确地定位UITextField,有必要引入一个'private'子类,以覆盖placeholderRectForBounds:]
改编自BobDickinson的斯威夫特回答。
在swift 5。工作很好。
class BaseTextView: UITextView {
// MARK: - Views
private var placeholderLabel: UIlabel!
// MARK: - Init
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setupUI()
startupSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
startupSetup()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
// MARK: - Setup UI
private extension BaseTextView {
func setupUI() {
addPlaceholderLabel()
textColor = .textColor
}
func addPlaceholderLabel() {
placeholderLabel = BaseLabel(frame: .zero)
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
insertSubview(placeholderLabel, at: 0)
placeholderLabel.alpha = 0
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = .clear
placeholderLabel.textColor = .lightTextColor
placeholderLabel.lineBreakMode = .byWordWrapping
placeholderLabel.isUserInteractionEnabled = false
placeholderLabel.font = UIFont.openSansSemibold.withSize(12)
placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
placeholderLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 5).isActive = true
placeholderLabel.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -8).isActive = true
placeholderLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8).isActive = true
}
}
// MARK: - Startup
private extension BaseTextView {
func startupSetup() {
addObservers()
textChanged(nil)
font = UIFont.openSansSemibold.withSize(12)
}
func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(textChanged(_:)), name: UITextView.textDidChangeNotification, object: nil)
}
}
// MARK: - Actions
private extension BaseTextView {
@objc func textChanged(_ sender: Notification?) {
UIView.animate(withDuration: 0.2) {
self.placeholderLabel.alpha = self.text.count == 0 ? 1 : 0
}
}
}
// MARK: - Public methods
extension BaseTextView {
public func setPlaceholder(_ placeholder: String) {
placeholderLabel.text = placeholder
}
}
还有一个简单的答案,使用CATextLayer。
添加CATextLayer到UITextView的层。 使用UITextViewDelegate方法,简单地改变CATextLayer的颜色。
func txtViewPlaceholder() {
let textlayer = CATextLayer()
textlayer.frame = CGRect(x: 5, y: 5, width: 200, height: 18)
textlayer.contentsScale = UIScreen.main.scale
textlayer.fontSize = 12
textlayer.alignmentMode = kCAAlignmentLeft
textlayer.string = "Enter here"
textlayer.isWrapped = true
textlayer.name = "placeholder"
textlayer.backgroundColor = UIColor.white.cgColor
textlayer.foregroundColor = UIColor.black.cgColor
yourTxtVw.layer.insertSublayer(textlayer, at: 0)
}
func removeAddPlaceholder(remove: Bool, textView: UITextView) {
for layers in textView.layer.sublayers! where layers.name == "placeholder" {
if remove {
(layers as! CATextLayer).foregroundColor = UIColor.white.cgColor
} else {
(layers as! CATextLayer).foregroundColor = UIColor.black.cgColor
}
}
}
extension YourViewController : UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
removeAddPlaceholder(remove: true, textView: textView)
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.count <= 0 {
removeAddPlaceholder(remove: false, textView: textView)
}
}
}
在看完所有答案后,我意识到我需要一些简单的,同时可重用的东西,这样我就可以在我的项目中为所有uitextview添加相同的功能。最后我得到了以下代码:
extension UITextView {
// MARK: TextView PlaceHolderLabel Setup
func createPlaceHolderLabel(with text: String) {
let lbl = UILabel()
self.addSubview(lbl)
// Add your constraints here
lbl.text = text
lbl.textColor = .lightGray
}
// My Textview contains only one UILabel, and for my use case the below code works, tweak it according to your use case
// Lastly two methods to toggle between show and hide the placeholder label
func hidePlaceHolderLabel() {
guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
lbl.isHidden = true
}
func showPlaceHolderLabel() {
guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
lbl.isHidden = false
}
}
当UILabel作为子视图添加到UITextView时,textView游标位置和UILabel位置将不匹配,因此在占位符文本之前留下一个空间
class ViewController: UIViewController, UITextViewDelegate {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
// Also setup textView constraints as per your need
// Add placeholder to your textView
// Leave one space before placeholder string
textView.createPlaceHolderLabel(with: " Address")
textView.delegate = self
}
然后在textViewdidChange方法中添加以下代码
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
textView.showPlaceHolderLabel()
} else {
textView.hidePlaceHolderLabel()
}
}
它可以在所有uitextview中重用。
下面是swift 3.1的代码
原始代码由杰森乔治在第一个答案。
不要忘记设置你的自定义类的TextView在接口生成器UIPlaceHolderTextView,然后设置占位符和占位符属性。
import UIKit
@IBDesignable
class UIPlaceHolderTextView: UITextView {
@IBInspectable var placeholder: String = ""
@IBInspectable var placeholderColor: UIColor = UIColor.lightGray
private let uiPlaceholderTextChangedAnimationDuration: Double = 0.05
private let defaultTagValue = 999
private var placeHolderLabel: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(
self,
selector: #selector(textChanged),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil
)
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
NotificationCenter.default.addObserver(
self,
selector: #selector(textChanged),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil
)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(
self,
selector: #selector(textChanged),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(
self,
name: NSNotification.Name.UITextViewTextDidChange,
object: nil
)
}
@objc private func textChanged() {
guard !placeholder.isEmpty else {
return
}
UIView.animate(withDuration: uiPlaceholderTextChangedAnimationDuration) {
if self.text.isEmpty {
self.viewWithTag(self.defaultTagValue)?.alpha = CGFloat(1.0)
}
else {
self.viewWithTag(self.defaultTagValue)?.alpha = CGFloat(0.0)
}
}
}
override var text: String! {
didSet{
super.text = text
textChanged()
}
}
override func draw(_ rect: CGRect) {
if !placeholder.isEmpty {
if placeHolderLabel == nil {
placeHolderLabel = UILabel.init(frame: CGRect(x: 0, y: 8, width: bounds.size.width - 16, height: 0))
placeHolderLabel!.lineBreakMode = .byWordWrapping
placeHolderLabel!.numberOfLines = 0
placeHolderLabel!.font = font
placeHolderLabel!.backgroundColor = UIColor.clear
placeHolderLabel!.textColor = placeholderColor
placeHolderLabel!.alpha = 0
placeHolderLabel!.tag = defaultTagValue
self.addSubview(placeHolderLabel!)
}
placeHolderLabel!.text = placeholder
placeHolderLabel!.sizeToFit()
self.sendSubview(toBack: placeHolderLabel!)
if text.isEmpty && !placeholder.isEmpty {
viewWithTag(defaultTagValue)?.alpha = 1.0
}
}
super.draw(rect)
}
}