我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。

如何做到这一点?


当前回答

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的斯威夫特回答。

其他回答

简单地创建UITextView的@IBDesignable子类:

@IBDesignable class AttributedTextView: UITextView {

    private let placeholderLabel = UILabel()

    @IBInspectable var placeholder: String = "" {

        didSet {

            setupPlaceholderLabelIfNeeded()
            textViewDidChange()
        }
    }

    override var text: String! {

        didSet {
            textViewDidChange()
        }
    }

    //MARK: - Initialization

    override func awakeFromNib() {
        super.awakeFromNib()

        setupPlaceholderLabelIfNeeded()
        NotificationCenter.default.addObserver(self, selector: #selector(textViewDidChange), name: .UITextViewTextDidChange, object: nil)
    }

    //MARK: - Deinitialization

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    //MARK: - Internal

    func textViewDidChange() {

        placeholderLabel.isHidden = !text.isEmpty
        layoutIfNeeded()
    }

    //MARK: - Private

    private func setupPlaceholderLabelIfNeeded() {

        placeholderLabel.removeFromSuperview()
        placeholderLabel.frame = CGRect(x: 0, y: 8, width: frame.size.width, height: 0)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.text = placeholder

        placeholderLabel.sizeToFit()

        insertSubview(placeholderLabel, at: 0)
    }
}

然后在标识检查器中设置占位符:

我能够做添加一个“占位符”到UITextView与很多少的代码。这就是我所做的:

UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(60, 800, 200, 60)];
textView.text = @"Write characters here...";
textView.textColor=[UIColor grayColor];
textView.font = [UIFont fontWithName:@"Hevlatica" size:15];
textView.delegate=self;

我猜这不是一个真正的占位符,因为你必须在写之前删除文本,但如果你想要一些更简单的东西,它可能会有帮助。

这里还有另一种方法,它复制了UITextField占位符的轻微缩进:

将UITextField拖到UITextView的右边,这样它们的左上角就对齐了。将占位符文本添加到文本字段。

在viewDidLoad中添加:

[tView setDelegate:self];
tView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);
tView.backgroundColor = [UIColor clearColor];

然后添加:

- (void)textViewDidChange:(UITextView *)textView {
    if (textView.text.length == 0) {
        textView.backgroundColor = [UIColor clearColor];            
    } else {
        textView.backgroundColor = [UIColor whiteColor];
    }
}

还有一个简单的答案,使用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的斯威夫特回答。