简体   繁体   English

UITextView 中的占位符

[英]Placeholder in UITextView

My application uses an UITextView .我的应用程序使用UITextView Now I want the UITextView to have a placeholder similar to the one you can set for an UITextField .现在我希望UITextView有一个类似于你可以为UITextField设置的占位符。

How to do this?这个怎么做?

I made a few minor modifications to bcd's solution to allow for initialization from a Xib file, text wrapping, and to maintain background color.我对 bcd 的解决方案做了一些小的修改,以允许从Xib文件初始化、文本换行和保持背景颜色。 Hopefully it will save others the trouble.希望它会为其他人省去麻烦。

UIPlaceHolderTextView.h: UIPlaceHolderTextView.h:

#import <Foundation/Foundation.h>
IB_DESIGNABLE
@interface UIPlaceHolderTextView : UITextView

@property (nonatomic, retain) IBInspectable NSString *placeholder;
@property (nonatomic, retain) IBInspectable UIColor *placeholderColor;

-(void)textChanged:(NSNotification*)notification;

@end

UIPlaceHolderTextView.m: UIPlaceHolderTextView.m:

#import "UIPlaceHolderTextView.h"

@interface UIPlaceHolderTextView ()

@property (nonatomic, retain) UILabel *placeHolderLabel;

@end

@implementation UIPlaceHolderTextView

CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
#if __has_feature(objc_arc)
#else
    [_placeHolderLabel release]; _placeHolderLabel = nil;
    [_placeholderColor release]; _placeholderColor = nil;
    [_placeholder release]; _placeholder = nil;
    [super dealloc];
#endif
}

- (void)awakeFromNib
{
    [super awakeFromNib];

    // Use Interface Builder User Defined Runtime Attributes to set
    // placeholder and placeholderColor in Interface Builder.
    if (!self.placeholder) {
        [self setPlaceholder:@""];
    }

    if (!self.placeholderColor) {
        [self setPlaceholderColor:[UIColor lightGrayColor]];
    }

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}

- (id)initWithFrame:(CGRect)frame
{
    if( (self = [super initWithFrame:frame]) )
    {
        [self setPlaceholder:@""];
        [self setPlaceholderColor:[UIColor lightGrayColor]];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
    }
    return self;
}

- (void)textChanged:(NSNotification *)notification
{
    if([[self placeholder] length] == 0)
    {
        return;
    }

    [UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{
    if([[self text] length] == 0)
    {
        [[self viewWithTag:999] setAlpha:1];
    }
    else
    {
        [[self viewWithTag:999] setAlpha:0];
    }
    }];
}

- (void)setText:(NSString *)text {
    [super setText:text];
    [self textChanged:nil];
}

- (void)drawRect:(CGRect)rect
{
    if( [[self placeholder] length] > 0 )
    {
        if (_placeHolderLabel == nil )
        {
            _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8,8,self.bounds.size.width - 16,0)];
            _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping;
            _placeHolderLabel.numberOfLines = 0;
            _placeHolderLabel.font = self.font;
            _placeHolderLabel.backgroundColor = [UIColor clearColor];
            _placeHolderLabel.textColor = self.placeholderColor;
            _placeHolderLabel.alpha = 0;
            _placeHolderLabel.tag = 999;
            [self addSubview:_placeHolderLabel];
        }

        _placeHolderLabel.text = self.placeholder;
        [_placeHolderLabel sizeToFit];
        [self sendSubviewToBack:_placeHolderLabel];
    }

    if( [[self text] length] == 0 && [[self placeholder] length] > 0 )
    {
        [[self viewWithTag:999] setAlpha:1];
    }

    [super drawRect:rect];
}

@end

Easy way, just create placeholder text in UITextView by using the following UITextViewDelegate methods:简单的方法,只需使用以下UITextViewDelegate方法在UITextView中创建占位符文本:

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:@"placeholder text here..."]) {
         textView.text = @"";
         textView.textColor = [UIColor blackColor]; //optional
    }
    [textView becomeFirstResponder];
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:@""]) {
        textView.text = @"placeholder text here...";
        textView.textColor = [UIColor lightGrayColor]; //optional
    }
    [textView resignFirstResponder];
}

just remember to set myUITextView with the exact text on creation eg只记得在创建时使用确切的文本设置myUITextView ,例如

UITextView *myUITextView = [[UITextView alloc] init];
myUITextView.delegate = self;
myUITextView.text = @"placeholder text here...";
myUITextView.textColor = [UIColor lightGrayColor]; //optional

and make the parent class a UITextViewDelegate before including these methods eg并在包含这些方法之前使父类成为UITextViewDelegate

@interface MyClass () <UITextViewDelegate>
@end

Code for Swift 3.1 Swift 3.1 的代码

func textViewDidBeginEditing(_ textView: UITextView) 
{
    if (textView.text == "placeholder text here..." && textView.textColor == .lightGray)
    {
        textView.text = ""
        textView.textColor = .black
    }
    textView.becomeFirstResponder() //Optional
}

func textViewDidEndEditing(_ textView: UITextView)
{
    if (textView.text == "")
    {
        textView.text = "placeholder text here..."
        textView.textColor = .lightGray
    }
    textView.resignFirstResponder()
}

just remember to set myUITextView with the exact text on creation eg只记得在创建时使用确切的文本设置myUITextView ,例如

 let myUITextView = UITextView.init()
 myUITextView.delegate = self
 myUITextView.text = "placeholder text here..."
 myUITextView.textColor = .lightGray

and make the parent class a UITextViewDelegate before including these methods eg并在包含这些方法之前使父类成为UITextViewDelegate

class MyClass: UITextViewDelegate
{

}

I wasn't too happy with any of the solutions posted as they were a bit heavy.我对发布的任何解决方案都不太满意,因为它们有点重。 Adding views to the view isn't really ideal (especially in drawRect: ).向视图添加视图并不是很理想(尤其是在drawRect:中)。 They both had leaks, which isn't acceptable either.他们都有泄漏,这也是不可接受的。

Here is my solution: SAMTextView这是我的解决方案: SAMTextView

SAMTextView.h SAMTextView.h

//
//  SAMTextView.h
//  SAMTextView
//
//  Created by Sam Soffes on 8/18/10.
//  Copyright 2010-2013 Sam Soffes. All rights reserved.
//

#import <UIKit/UIKit.h>

/**
 UITextView subclass that adds placeholder support like UITextField has.
 */
@interface SAMTextView : UITextView

/**
 The string that is displayed when there is no other text in the text view.

 The default value is `nil`.
 */
@property (nonatomic, strong) NSString *placeholder;

/**
 The color of the placeholder.

 The default is `[UIColor lightGrayColor]`.
 */
@property (nonatomic, strong) UIColor *placeholderTextColor;

/**
 Returns the drawing rectangle for the text views’s placeholder text.

 @param bounds The bounding rectangle of the receiver.
 @return The computed drawing rectangle for the placeholder text.
 */
- (CGRect)placeholderRectForBounds:(CGRect)bounds;

@end

SAMTextView.m SAMTextView.m

//
//  SAMTextView.m
//  SAMTextView
//
//  Created by Sam Soffes on 8/18/10.
//  Copyright 2010-2013 Sam Soffes. All rights reserved.
//

#import "SAMTextView.h"

@implementation SAMTextView

#pragma mark - Accessors

@synthesize placeholder = _placeholder;
@synthesize placeholderTextColor = _placeholderTextColor;

- (void)setText:(NSString *)string {
  [super setText:string];
  [self setNeedsDisplay];
}


- (void)insertText:(NSString *)string {
  [super insertText:string];
  [self setNeedsDisplay];
}


- (void)setAttributedText:(NSAttributedString *)attributedText {
  [super setAttributedText:attributedText];
  [self setNeedsDisplay];
}


- (void)setPlaceholder:(NSString *)string {
  if ([string isEqual:_placeholder]) {
    return;
  }

  _placeholder = string;
  [self setNeedsDisplay];
}


- (void)setContentInset:(UIEdgeInsets)contentInset {
  [super setContentInset:contentInset];
  [self setNeedsDisplay];
}


- (void)setFont:(UIFont *)font {
  [super setFont:font];
  [self setNeedsDisplay];
}


- (void)setTextAlignment:(NSTextAlignment)textAlignment {
  [super setTextAlignment:textAlignment];
  [self setNeedsDisplay];
}


#pragma mark - NSObject

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];
}


#pragma mark - UIView

- (id)initWithCoder:(NSCoder *)aDecoder {
  if ((self = [super initWithCoder:aDecoder])) {
    [self initialize];
  }
  return self;
}


- (id)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])) {
    [self initialize];
  }
  return self;
}


- (void)drawRect:(CGRect)rect {
  [super drawRect:rect];

  if (self.text.length == 0 && self.placeholder) {
    rect = [self placeholderRectForBounds:self.bounds];

    UIFont *font = self.font ? self.font : self.typingAttributes[NSFontAttributeName];

    // Draw the text
    [self.placeholderTextColor set];
    [self.placeholder drawInRect:rect withFont:font lineBreakMode:NSLineBreakByTruncatingTail alignment:self.textAlignment];
  }
}


#pragma mark - Placeholder

- (CGRect)placeholderRectForBounds:(CGRect)bounds {
  // Inset the rect
  CGRect rect = UIEdgeInsetsInsetRect(bounds, self.contentInset);

  if (self.typingAttributes) {
    NSParagraphStyle *style = self.typingAttributes[NSParagraphStyleAttributeName];
    if (style) {
      rect.origin.x += style.headIndent;
      rect.origin.y += style.firstLineHeadIndent;
    }
  }

  return rect;
}


#pragma mark - Private

- (void)initialize {
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:self];

  self.placeholderTextColor = [UIColor colorWithWhite:0.702f alpha:1.0f];
}


- (void)textChanged:(NSNotification *)notification {
  [self setNeedsDisplay];
}

@end

It's a lot simpler than the others, as it doesn't use subviews (or have leaks).它比其他的简单得多,因为它不使用子视图(或有泄漏)。 Feel free to use it.随意使用它。

Update 11/10/11: It is now documented and supports use in Interface Builder. 2011 年11 月 10 日更新:现在已记录并支持在 Interface Builder 中使用。

Update 11/24/13: Point to new repo. 2013 年 11 月 24 日更新:指向新的仓库。

I found myself a very easy way to imitate a place-holder我发现自己可以很容易地模仿占位符

  1. in the NIB or code set your textView's textColor to lightGrayColor (most of the time)在 NIB 或代码中将您的 textView 的 textColor 设置为 lightGrayColor(大多数情况下)
  2. make sure that your textView's delegate is linked to file's owner and implement UITextViewDelegate in your header file确保你的 textView 的委托链接到文件的所有者并在你的头文件中实现 UITextViewDelegate
  3. set the default text of your text view to (example: "Foobar placeholder")将文本视图的默认文本设置为(例如:“Foobar 占位符”)
  4. implement: (BOOL) textViewShouldBeginEditing:(UITextView *)textView实现:(BOOL) textViewShouldBeginEditing:(UITextView *)textView

Edit:编辑:

Changed if statements to compare tags rather than text.更改 if 语句以比较标签而不是文本。 If the user deleted their text it was possible to also accidentally delete a portion of the place holder @"Foobar placeholder" .This meant if the user re-entered the textView the following delegate method, -(BOOL) textViewShouldBeginEditing:(UITextView *) textView , it would not work as expected.如果用户删除了他们的文本,也可能不小心删除了@"Foobar placeholder"的一部分。这意味着如果用户重新输入 textView 以下委托方法, -(BOOL) textViewShouldBeginEditing:(UITextView *) textView ,它不会按预期工作。 I tried comparing by the color of the text in the if statement but found that light grey color set in interface builder is not the same as light grey color set in code with [UIColor lightGreyColor]我尝试通过 if 语句中文本的颜色进行比较,但发现界面生成器中设置的浅灰色与[UIColor lightGreyColor]代码中设置的浅灰色不同

- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
    if(textView.tag == 0) {
        textView.text = @"";
        textView.textColor = [UIColor blackColor];
        textView.tag = 1;
    }
    return YES;
}

It is also possible to reset the placeholder text when the keyboard returns and the [textView length] == 0也可以在键盘返回且 [textView 长度] == 0 时重置占位符文本

EDIT:编辑:

Just to make the last part clearer - here's is how you can set the placeholder text back:只是为了使最后一部分更清楚 - 这是您如何设置占位符文本的方法:

- (void)textViewDidChange:(UITextView *)textView
{
   if([textView.text length] == 0)
   {
       textView.text = @"Foobar placeholder";
       textView.textColor = [UIColor lightGrayColor];
       textView.tag = 0;
   }
}

What you can do is set up the text view with some initial value in the text property, and change the textColor to [UIColor grayColor] or something similar.您可以做的是在text属性中使用一些初始值设置文本视图,并将textColor更改为[UIColor grayColor]或类似的东西。 Then, whenever the text view becomes editable, clear the text and present a cursor, and if the text field is ever empty again, put your placeholder text back.然后,每当文本视图变为可编辑时,清除文本并显示光标,如果文本字段再次为空,请将占位符文本放回原处。 Change the color to [UIColor blackColor] as appropriate.根据需要将颜色更改为[UIColor blackColor]

It's not exactly the same as the placeholder functionality in a UITextField, but it's close.它与 UITextField 中的占位符功能并不完全相同,但很接近。

You can set the label on the UITextView by您可以通过在UITextView上设置标签

[UITextView addSubView:lblPlaceHoldaer];

and hide it on TextViewdidChange method.并将其隐藏在TextViewdidChange方法上。

This is the simple & easy way.这是简单易行的方法。

Simple Swift 3 solution简单的 Swift 3解决方案

Add UITextViewDelegate to your classUITextViewDelegate添加到您的班级

Set yourTextView.delegate = self设置yourTextView.delegate = self

Create placeholderLabel and position it inside yourTextView创建placeholderLabel并将其放置在yourTextView

Now just animate placeholderLabel.alpha on textViewDidChange :现在只需在textViewDidChange上为placeholderLabel.alpha设置动画:

  func textViewDidChange(_ textView: UITextView) {
    let newAlpha: CGFloat = textView.text.isEmpty ? 1 : 0
    if placeholderLabel.alpha != newAlpha {
      UIView.animate(withDuration: 0.3) {
        self.placeholderLabel.alpha = newAlpha
      }
    }
  }

you might have to play with placeholderLabel position to set it up right, but that shouldn't be too hard您可能必须使用placeholderLabel位置才能正确设置它,但这应该不会太难

If someone needs a Solution for Swift:如果有人需要 Swift 解决方案:

Add UITextViewDelegate to your class将 UITextViewDelegate 添加到您的班级

var placeHolderText = "Placeholder Text..."

override func viewDidLoad() {
    super.viewDidLoad()
    textView.delegate = self
}

func textViewShouldBeginEditing(textView: UITextView) -> Bool {

    self.textView.textColor = .black

    if(self.textView.text == placeHolderText) {
        self.textView.text = ""
    }

    return true
}

func textViewDidEndEditing(textView: UITextView) {
    if(textView.text == "") {
        self.textView.text = placeHolderText
        self.textView.textColor = .lightGray
    }
}

override func viewWillAppear(animated: Bool) {

    if(currentQuestion.answerDisplayValue == "") {
        self.textView.text = placeHolderText
        self.textView.textColor = .lightGray
    } else {
        self.textView.text = "xxx" // load default text / or stored 
        self.textView.textColor = .black
    }
}

I extended KmKndy's answer, so that the placeholder remains visible until the user starts editing the UITextView rather than just taps on it.我扩展了 KmKndy 的答案,以便占位符保持可见,直到用户开始编辑UITextView而不仅仅是点击它。 This mirrors the functionality in the Twitter and Facebook apps.这反映了 Twitter 和 Facebook 应用程序中的功能。 My solution doesn't require you to subclass and works if the user types directly or pastes text!如果用户直接键入或粘贴文本,我的解决方案不需要您进行子类化和工作!

占位符示例推特应用

- (void)textViewDidChangeSelection:(UITextView *)textView{
    if ([textView.text isEqualToString:@"What's happening?"] && [textView.textColor isEqual:[UIColor lightGrayColor]])[textView setSelectedRange:NSMakeRange(0, 0)];

}

- (void)textViewDidBeginEditing:(UITextView *)textView{

    [textView setSelectedRange:NSMakeRange(0, 0)];
}

- (void)textViewDidChange:(UITextView *)textView
{
    if (textView.text.length != 0 && [[textView.text substringFromIndex:1] isEqualToString:@"What's happening?"] && [textView.textColor isEqual:[UIColor lightGrayColor]]){
        textView.text = [textView.text substringToIndex:1];
        textView.textColor = [UIColor blackColor]; //optional

    }
    else if(textView.text.length == 0){
        textView.text = @"What's happening?";
        textView.textColor = [UIColor lightGrayColor];
        [textView setSelectedRange:NSMakeRange(0, 0)];
    }
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:@""]) {
        textView.text = @"What's happening?";
        textView.textColor = [UIColor lightGrayColor]; //optional
    }
    [textView resignFirstResponder];
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
    if (textView.text.length > 1 && [textView.text isEqualToString:@"What's happening?"]) {
         textView.text = @"";
         textView.textColor = [UIColor blackColor];
    }

    return YES;
}

just remember to set myUITextView with the exact text on creation eg只记得在创建时使用确切的文本设置 myUITextView,例如

UITextView *myUITextView = [[UITextView alloc] init];
myUITextView.delegate = self;
myUITextView.text = @"What's happening?";
myUITextView.textColor = [UIColor lightGrayColor]; //optional

and make the parent class a UITextView delegate before including these methods eg并在包含这些方法之前使父类成为 UITextView 委托,例如

@interface MyClass () <UITextViewDelegate>
@end

Below is a Swift port of "SAMTextView" ObjC code posted as one of the first handful of replies to the question.下面是“SAMTextView”ObjC 代码的 Swift 端口,作为对该问题的首批回复之一。 I tested it on iOS 8. I tweaked a couple of things, including the bounds offset for the placement of the placeholder text, as the original was too high and too far right (used suggestion in one of the comments to that post).我在 iOS 8 上对其进行了测试。我调整了一些东西,包括用于放置占位符文本的边界偏移,因为原来的位置太高而且太靠右(在该帖子的一个评论中使用了建议)。

I know there are a lot of simple solutions, but I like the approach of subclassing UITextView because it's reusable and I don't have to clutter classes utilizing it with the mechanisms.我知道有很多简单的解决方案,但我喜欢子类化 UITextView 的方法,因为它是可重用的,而且我不必将使用它的类与机制混为一谈。

Swift 2.2:斯威夫特 2.2:

import UIKit

class PlaceholderTextView: UITextView {

    @IBInspectable var placeholderColor: UIColor = UIColor.lightGrayColor()
    @IBInspectable var placeholderText: String = ""

    override var font: UIFont? {
        didSet {
            setNeedsDisplay()
        }
    }

    override var contentInset: UIEdgeInsets {
        didSet {
            setNeedsDisplay()
        }
    }

    override var textAlignment: NSTextAlignment {
        didSet {
            setNeedsDisplay()
        }
    }

    override var text: String? {
        didSet {
            setNeedsDisplay()
        }
    }

    override var attributedText: NSAttributedString? {
        didSet {
            setNeedsDisplay()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUp()
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
    }

    private func setUp() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PlaceholderTextView.textChanged(_:)),
                                                         name: UITextViewTextDidChangeNotification, object: self)
    }

    func textChanged(notification: NSNotification) {
        setNeedsDisplay()
    }

    func placeholderRectForBounds(bounds: CGRect) -> CGRect {
        var x = contentInset.left + 4.0
        var y = contentInset.top  + 9.0
        let w = frame.size.width - contentInset.left - contentInset.right - 16.0
        let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0

        if let style = self.typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
            x += style.headIndent
            y += style.firstLineHeadIndent
        }
        return CGRect(x: x, y: y, width: w, height: h)
    }

    override func drawRect(rect: CGRect) {
        if text!.isEmpty && !placeholderText.isEmpty {
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = textAlignment
            let attributes: [ String: AnyObject ] = [
                NSFontAttributeName : font!,
                NSForegroundColorAttributeName : placeholderColor,
                NSParagraphStyleAttributeName  : paragraphStyle]

            placeholderText.drawInRect(placeholderRectForBounds(bounds), withAttributes: attributes)
        }
        super.drawRect(rect)
    }
}

Swift 4.2:斯威夫特 4.2:

import UIKit

class PlaceholderTextView: UITextView {

    @IBInspectable var placeholderColor: UIColor = UIColor.lightGray
    @IBInspectable var placeholderText: String = ""

    override var font: UIFont? {
        didSet {
            setNeedsDisplay()
        }
    }

    override var contentInset: UIEdgeInsets {
        didSet {
            setNeedsDisplay()
        }
    }

    override var textAlignment: NSTextAlignment {
        didSet {
            setNeedsDisplay()
        }
    }

    override var text: String? {
        didSet {
            setNeedsDisplay()
        }
    }

    override var attributedText: NSAttributedString? {
        didSet {
            setNeedsDisplay()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUp()
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
    }

    private func setUp() {
        NotificationCenter.default.addObserver(self,
         selector: #selector(self.textChanged(notification:)),
         name: Notification.Name("UITextViewTextDidChangeNotification"),
         object: nil)
    }

    @objc func textChanged(notification: NSNotification) {
        setNeedsDisplay()
    }

    func placeholderRectForBounds(bounds: CGRect) -> CGRect {
        var x = contentInset.left + 4.0
        var y = contentInset.top  + 9.0
        let w = frame.size.width - contentInset.left - contentInset.right - 16.0
        let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0

        if let style = self.typingAttributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
            x += style.headIndent
            y += style.firstLineHeadIndent
        }
        return CGRect(x: x, y: y, width: w, height: h)
    }

    override func draw(_ rect: CGRect) {
        if text!.isEmpty && !placeholderText.isEmpty {
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = textAlignment
            let attributes: [NSAttributedString.Key: Any] = [
            NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue) : font!,
            NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : placeholderColor,
            NSAttributedString.Key(rawValue: NSAttributedString.Key.paragraphStyle.rawValue)  : paragraphStyle]

            placeholderText.draw(in: placeholderRectForBounds(bounds: bounds), withAttributes: attributes)
        }
        super.draw(rect)
    }
}

I recommend to use SZTextView .我建议使用SZTextView

https://github.com/glaszig/SZTextView https://github.com/glaszig/SZTextView

Add your default UITextView from storyboard and then change its custom class to SZTextView like below 👇👇👇👇storyboard提要中添加默认的UITextView ,然后将其自定义类更改为SZTextView ,如下所示👇👇👇👇

在此处输入图像描述

Then you will see two new option in the Attribute Inspector 👇👇👇👇然后你会在Attribute Inspector中看到两个新选项👇👇👇👇

在此处输入图像描述

Here's a way easier solution that behaves exactly like UITextField's placeholder but doesn't require drawing custom views, or resigning first responder.这是一种更简单的解决方案,其行为与 UITextField 的占位符完全相同,但不需要绘制自定义视图或让第一响应者辞职。

- (void) textViewDidChange:(UITextView *)textView{

    if (textView.text.length == 0){
        textView.textColor = [UIColor lightGrayColor];
        textView.text = placeholderText;
        [textView setSelectedRange:NSMakeRange(0, 0)];
        isPlaceholder = YES;

    } else if (isPlaceholder && ![textView.text isEqualToString:placeholderText]) {
        textView.text = [textView.text substringToIndex:1];
        textView.textColor = [UIColor blackColor];
        isPlaceholder = NO;
    }

}

(the second check in the else if statement is for the case where nothing is entered and the user presses backspace) (else if 语句中的第二次检查是针对未输入任何内容且用户按退格键的情况)

Just set your class as a UITextViewDelegate.只需将您的课程设置为 UITextViewDelegate。 In viewDidLoad you should initialize like在 viewDidLoad 你应该初始化像

- (void) viewDidLoad{
    // initialize placeholder text
    placeholderText = @"some placeholder";
    isPlaceholder = YES;
    self.someTextView.text = placeholderText;
    self.someTextView.textColor = [UIColor lightGrayColor];
    [self.someTextView setSelectedRange:NSMakeRange(0, 0)];

    // assign UITextViewDelegate
    self.someTextView.delegate = self;
}

this is how I did it:我就是这样做的:

UITextView2.h UITextView2.h

#import <UIKit/UIKit.h>

@interface UITextView2 : UITextView <UITextViewDelegate> {
 NSString *placeholder;
 UIColor *placeholderColor;
}

@property(nonatomic, retain) NSString *placeholder;
@property(nonatomic, retain) UIColor *placeholderColor;

-(void)textChanged:(NSNotification*)notif;

@end

UITextView2.m UITextView2.m

@implementation UITextView2

@synthesize placeholder, placeholderColor;

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self setPlaceholder:@""];
        [self setPlaceholderColor:[UIColor lightGrayColor]];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
    }
    return self;
}

-(void)textChanged:(NSNotification*)notif {
    if ([[self placeholder] length]==0)
        return;
    if ([[self text] length]==0) {
        [[self viewWithTag:999] setAlpha:1];
    } else {
        [[self viewWithTag:999] setAlpha:0];
    }

}

- (void)drawRect:(CGRect)rect {
    if ([[self placeholder] length]>0) {
        UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, 0, 0)];
        [l setFont:self.font];
        [l setTextColor:self.placeholderColor];
        [l setText:self.placeholder];
        [l setAlpha:0];
        [l setTag:999];
        [self addSubview:l];
        [l sizeToFit];
        [self sendSubviewToBack:l];
        [l release];
    }
    if ([[self text] length]==0 && [[self placeholder] length]>0) {
        [[self viewWithTag:999] setAlpha:1];
    }
    [super drawRect:rect];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}


@end

Hi you can use IQTextView available in IQKeyboard Manager it's simple to use and integrate just set class of your textview to IQTextView and you can use its property for setting placeholder label with color you want.嗨,您可以使用 IQKeyboard Manager 中提供的 IQTextView,它使用简单,只需将 textview 的设置类集成到 IQTextView,您可以使用它的属性来设置占位符标签和您想要的颜色。 You can download the library from IQKeyboardManager您可以从IQKeyboardManager下载库

or you can install it from cocoapods.或者你可以从 cocoapods 安装它。

Sorry to add another answer, But I just pulled something like this off and this created the closest-to-UITextField kind of placeholder.很抱歉添加另一个答案,但我刚刚取消了这样的内容,这创建了最接近 UITextField 的占位符。

Hope this helps someone.希望这可以帮助某人。

-(void)textViewDidChange:(UITextView *)textView{
    if(textView.textColor == [UIColor lightGrayColor]){
        textView.textColor  = [UIColor blackColor]; // look at the comment section in this answer
        textView.text       = [textView.text substringToIndex: 0];// look at the comment section in this answer
    }else if(textView.text.length == 0){
        textView.text       = @"This is some placeholder text.";
        textView.textColor  = [UIColor lightGrayColor];
        textView.selectedRange = NSMakeRange(0, 0);
    }
}

-(void)textViewDidChangeSelection:(UITextView *)textView{
    if(textView.textColor == [UIColor lightGrayColor] && (textView.selectedRange.location != 0 || textView.selectedRange.length != 0)){
        textView.selectedRange = NSMakeRange(0, 0);
    }
}

Simple way to use this within some line of code:在某行代码中使用它的简单方法:

Take one label up to UITextView in .nib connecting this label to your code , After it.在 .nib 中将一个标签带到 UITextView ,然后将此标签连接到您的代码。

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{

    if (range.location>0 || text.length!=0) {
        placeholderLabel1.hidden = YES;
    }else{
        placeholderLabel1.hidden = NO;
    }
    return YES;
}

I've modified Sam Soffes' implementation to work with iOS7:我已经修改了 Sam Soffes 的实现以使用 iOS7:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];

    if (_shouldDrawPlaceholder)
    {
        UIEdgeInsets insets = self.textContainerInset;        
        CGRect placeholderRect = CGRectMake(
                insets.left + self.textContainer.lineFragmentPadding,
                insets.top,
                self.frame.size.width - insets.left - insets.right,
                self.frame.size.height - insets.top - insets.bottom);

        [_placeholderText drawWithRect:placeholderRect
                           options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
                        attributes:self.placeholderAttributes
                           context:nil];
    }
}

- (NSDictionary *)placeholderAttributes
{
    if (_placeholderAttributes == nil)
    {
        _placeholderAttributes = @
        {
            NSFontAttributeName : self.font,
            NSForegroundColorAttributeName : self.placeholderColor
        };
    }

    return _placeholderAttributes;
}

Remember to set _placeholderAttribues = nil in methods that might change the font and other thigns that might affect them.请记住在可能会更改字体和其他可能影响它们的方法中设置_placeholderAttribues = nil You might also want to skip "lazy" making of the attributes dictionary if that doesn't bug you.如果这不会打扰您,您可能还想跳过属性字典的“懒惰”制作。

EDIT:编辑:

Remember to call setNeedsDisplay in a overridden version of setBounds if you like the placeholder to look good after autolayout animations and the like.如果您希望占位符在自动布局动画等之后看起来不错,请记住在 setBounds 的覆盖版本中调用 setNeedsDisplay。

This mimics UITextField's placeholder perfectly, where the place holder text stays until you actually type something.这完美地模仿了 UITextField 的占位符,占位符文本一直保留到您实际键入内容为止。

private let placeholder = "Type here"

@IBOutlet weak var textView: UITextView! {
    didSet {
        textView.textColor = UIColor.lightGray
        textView.text = placeholder
        textView.selectedRange = NSRange(location: 0, length: 0)
    }
}

extension ViewController: UITextViewDelegate {

    func textViewDidChangeSelection(_ textView: UITextView) {
        // Move cursor to beginning on first tap
        if textView.text == placeholder {
            textView.selectedRange = NSRange(location: 0, length: 0)
        }
    }

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if textView.text == placeholder && !text.isEmpty {
            textView.text = nil
            textView.textColor = UIColor.black
            textView.selectedRange = NSRange(location: 0, length: 0)
        }
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        if textView.text.isEmpty {
            textView.textColor = UIColor.lightGray
            textView.text = placeholder
        }
    }
}

You could also create a new class TextViewWithPlaceholder as a subclass of UITextView.您还可以创建一个新类 TextViewWithPlaceholder 作为 UITextView 的子类。

(This code is kind of rough -- but I think it's on the right track.) (这段代码有点粗糙——但我认为它在正确的轨道上。)

@interface TextViewWithPlaceholder : UITextView
{

    NSString *placeholderText;  // make a property
    UIColor *placeholderColor;  // make a property
    UIColor *normalTextColor;   // cache text color here whenever you switch to the placeholderColor
}

- (void) setTextColor: (UIColor*) color
{
   normalTextColor = color;
   [super setTextColor: color];
}

- (void) updateForTextChange
{
    if ([self.text length] == 0)
    { 
        normalTextColor = self.textColor;
        self.textColor = placeholderColor;
        self.text = placeholderText;
    }
    else
    {
        self.textColor = normalTextColor;
    }

}

In your delegate, add this:在您的委托中,添加以下内容:

- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView respondsToSelector: @selector(updateForTextChange)])
    {
        [textView updateForTextChange];
    }

}

This thread has had plenty of answers, but here's the version I prefer.这个帖子有很多答案,但这是我更喜欢的版本。

It extends the existing UITextView class so is easily reuseable, and it doesn't intercept the events like textViewDidChange (which might break user's code, if they were already intercepting these events elsewhere).扩展了现有的UITextView类,因此很容易重用,并且它不会拦截诸如textViewDidChange之类的事件(如果他们已经在其他地方拦截了这些事件,则可能会破坏用户的代码)。

Using my code (shown below), you can easily add a placeholder to any of your UITextViews like this:使用我的代码(如下所示),您可以轻松地将占位符添加到任何UITextViews ,如下所示:

self.textViewComments.placeholder = @"(Enter some comments here.)";

When you set this new placeholder value, it quietly adds a UILabel on top of your UITextView , then hide/shows it as necessary:当您设置这个新的占位符值时,它会悄悄地在您的UITextView顶部添加一个UILabel ,然后根据需要隐藏/显示它:

在此处输入图像描述

Okay, to make these changes, add a "UITextViewHelper.h" file containing this code:好的,要进行这些更改,请添加包含以下代码的“UITextViewHelper.h”文件:

//  UITextViewHelper.h
//  Created by Michael Gledhill on 13/02/15.

#import <Foundation/Foundation.h>

@interface UITextView (UITextViewHelper)

@property (nonatomic, strong) NSString* placeholder;
@property (nonatomic, strong) UILabel* placeholderLabel;
@property (nonatomic, strong) NSString* textValue;

-(void)checkIfNeedToDisplayPlaceholder;

@end

...and a UITextViewHelper.m file containing this: ...以及包含以下内容的 UITextViewHelper.m 文件:

//  UITextViewHelper.m
//  Created by Michael Gledhill on 13/02/15.
//
//  This UITextView category allows us to easily display a PlaceHolder string in our UITextView.
//  The downside is that, your code needs to set the "textValue" rather than the "text" value to safely set the UITextView's text.
//
#import "UITextViewHelper.h"
#import <objc/runtime.h>

@implementation UITextView (UITextViewHelper)

#define UI_PLACEHOLDER_TEXT_COLOR [UIColor colorWithRed:170.0/255.0 green:170.0/255.0 blue:170.0/255.0 alpha:1.0]

@dynamic placeholder;
@dynamic placeholderLabel;
@dynamic textValue;

-(void)setTextValue:(NSString *)textValue
{
    //  Change the text of our UITextView, and check whether we need to display the placeholder.
    self.text = textValue;
    [self checkIfNeedToDisplayPlaceholder];
}
-(NSString*)textValue
{
    return self.text;
}

-(void)checkIfNeedToDisplayPlaceholder
{
    //  If our UITextView is empty, display our Placeholder label (if we have one)
    if (self.placeholderLabel == nil)
        return;

    self.placeholderLabel.hidden = (![self.text isEqualToString:@""]);
}

-(void)onTap
{
    //  When the user taps in our UITextView, we'll see if we need to remove the placeholder text.
    [self checkIfNeedToDisplayPlaceholder];

    //  Make the onscreen keyboard appear.
    [self becomeFirstResponder];
}

-(void)keyPressed:(NSNotification*)notification
{
    //  The user has just typed a character in our UITextView (or pressed the delete key).
    //  Do we need to display our Placeholder label ?
   [self checkIfNeedToDisplayPlaceholder];
}

#pragma mark - Add a "placeHolder" string to the UITextView class

NSString const *kKeyPlaceHolder = @"kKeyPlaceHolder";
-(void)setPlaceholder:(NSString *)_placeholder
{
    //  Sets our "placeholder" text string, creates a new UILabel to contain it, and modifies our UITextView to cope with
    //  showing/hiding the UILabel when needed.
    objc_setAssociatedObject(self, &kKeyPlaceHolder, (id)_placeholder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    self.placeholderLabel = [[UILabel alloc] initWithFrame:self.frame];
    self.placeholderLabel.numberOfLines = 1;
    self.placeholderLabel.text = _placeholder;
    self.placeholderLabel.textColor = UI_PLACEHOLDER_TEXT_COLOR;
    self.placeholderLabel.backgroundColor = [UIColor clearColor];
    self.placeholderLabel.userInteractionEnabled = true;
    self.placeholderLabel.font = self.font;
    [self addSubview:self.placeholderLabel];

    [self.placeholderLabel sizeToFit];

    //  Whenever the user taps within the UITextView, we'll give the textview the focus, and hide the placeholder if necessary.
    [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)]];

    //  Whenever the user types something in the UITextView, we'll see if we need to hide/show the placeholder label.
    [[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];

    [self checkIfNeedToDisplayPlaceholder];
}
-(NSString*)placeholder
{
    //  Returns our "placeholder" text string
    return objc_getAssociatedObject(self, &kKeyPlaceHolder);
}

#pragma mark - Add a "UILabel" to this UITextView class

NSString const *kKeyLabel = @"kKeyLabel";
-(void)setPlaceholderLabel:(UILabel *)placeholderLabel
{
    //  Stores our new UILabel (which contains our placeholder string)
    objc_setAssociatedObject(self, &kKeyLabel, (id)placeholderLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    [[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];

    [self checkIfNeedToDisplayPlaceholder];
}
-(UILabel*)placeholderLabel
{
    //  Returns our new UILabel
    return objc_getAssociatedObject(self, &kKeyLabel);
}
@end

Yup, it's a lot of code, but once you've added it to your project and included the .h file...是的,它有很多代码,但是一旦你将它添加到你的项目中并包含 .h 文件......

#import "UITextViewHelper.h"

...you can easily use placeholders in UITextViews . ...您可以轻松地在UITextViews中使用占位符。

There's one gotcha though.不过有一个问题。

If you do this:如果你这样做:

self.textViewComments.placeholder = @"(Enter some comments here.)";
self.textViewComments.text = @"Ooooh, hello there";

...the placeholder will appear on top of the text. ...占位符将出现在文本的顶部 When you set the text value, none of the regular notifications gets called, so I couldn't work out how to call my function to decide whether to show/hide the placeholder.当您设置text值时,不会调用任何常规通知,因此我无法弄清楚如何调用我的函数来决定是否显示/隐藏占位符。

The solution is to set the textValue rather than text :解决方案是设置textValue而不是text

self.textViewComments.placeholder = @"(Enter some comments here.)";
self.textViewComments.textValue = @"Ooooh, hello there";

Alternatively, you can set the text value, then call checkIfNeedToDisplayPlaceholder .或者,您可以设置text值,然后调用checkIfNeedToDisplayPlaceholder

self.textViewComments.text = @"Ooooh, hello there";
[self.textViewComments checkIfNeedToDisplayPlaceholder];

I like solutions like this, as they "fill the gap" between what Apple provides us with, and what we (as developers) actually need in our apps.我喜欢这样的解决方案,因为它们“填补了”Apple 为我们提供的内容与我们(作为开发人员)在应用程序中实际需要的内容之间的差距。 You write this code once, add it to your library of "helper" .m/.h files, and, over time, the SDK actually starts becoming less frustrating.您编写此代码一次,将其添加到您的“帮助”.m/.h 文件库中,随着时间的推移,SDK 实际上开始变得不那么令人沮丧了。

(I wrote a similar helper for adding a "clear" button to my UITextViews, another thing which annoyingly exists in UITextField but not in UITextView ...) (我写了一个类似的帮助程序来为我的 UITextViews 添加一个“清除”按钮,另一个令人讨厌的东西存在于UITextField但不存在于UITextView中......)

First take a label in .h file.首先在 .h 文件中取一个标签。

Here I take我在这里取

UILabel * lbl;

Then in .m under viewDidLoad declare it然后在 .m 下 viewDidLoad 声明它

lbl = [[UILabel alloc] initWithFrame:CGRectMake(8.0, 0.0,250, 34.0)];

lbl.font=[UIFont systemFontOfSize:14.0];

[lbl setText:@"Write a message..."];

[lbl setBackgroundColor:[UIColor clearColor]];

[lbl setTextColor:[UIColor lightGrayColor]];

[textview addSubview:lbl];

textview is my TextView.文本视图是我的文本视图。

Now declare现在声明

-(void)textViewDidChange:(UITextView *)textView {

 if (![textView hasText]){

    lbl.hidden = NO;

 }
 else{
    lbl.hidden = YES;
 }

}

And your Textview placeholder is ready !您的 Textview 占位符已准备就绪!

I recommend use pod 'UITextView+Placeholder'我建议使用 pod 'UITextView+Placeholder'

pod 'UITextView+Placeholder'

on your code在你的代码上

#import "UITextView+Placeholder.h"

////    

UITextView *textView = [[UITextView alloc] init];
textView.placeholder = @"How are you?";
textView.placeholderColor = [UIColor lightGrayColor];

Based on some of the great suggestions here already, I was able to put together the following lightweight, Interface-Builder-compatible subclass of UITextView , which:基于这里的一些很好的建议,我能够将以下轻量级、与 Interface-Builder 兼容的UITextView子类放在一起,其中:

  • Includes configurable placeholder text, styled just like that of UITextField .包括可配置的占位符文本,其样式与UITextField类似。
  • Doesn't require any additional subviews or constraints.不需要任何额外的子视图或约束。
  • Doesn't require any delegation or other behaviour from the ViewController.不需要来自 ViewController 的任何委托或其他行为。
  • Doesn't require any notifications.不需要任何通知。
  • Keeps that text fully separated from any outside classes looking at the field's text property.使该文本与查看该字段的text属性的任何外部类完全分离。

Improvement suggestions are welcome.欢迎提出改进建议。

Edit 1: Updated to reset placeholder formatting if actual text is set programmatically.编辑 1:如果以编程方式设置实际文本,则更新为重置占位符格式。

Edit 2: The placeholder text color can now be retrieved programmatically.编辑 2:现在可以以编程方式检索占位符文本颜色。

Swift v5:斯威夫特 v5:

import UIKit
@IBDesignable class TextViewWithPlaceholder: UITextView {
    
    override var text: String! { // Ensures that the placeholder text is never returned as the field's text
        get {
            if showingPlaceholder {
                return "" // When showing the placeholder, there's no real text to return
            } else { return super.text }
        }
        set {
            if showingPlaceholder {
                removePlaceholderFormatting() // If the placeholder text is what's being changed, it's no longer the placeholder
            }
            super.text = newValue
        }
    }
    @IBInspectable var placeholderText: String = ""
    @IBInspectable var placeholderTextColor: UIColor = .placeholderText
    private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        if text.isEmpty {
            showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
        }
    }
    
    override public func becomeFirstResponder() -> Bool {
        
        // If the current text is the placeholder, remove it
        if showingPlaceholder {
            text = nil
            removePlaceholderFormatting()
        }
        return super.becomeFirstResponder()
    }
    
    override public func resignFirstResponder() -> Bool {
        
        // If there's no text, put the placeholder back
        if text.isEmpty {
            showPlaceholderText()
        }
        return super.resignFirstResponder()
    }
    
    private func showPlaceholderText() {
        
        text = placeholderText
        showingPlaceholder = true
        textColor = placeholderTextColor
    }
    
    private func removePlaceholderFormatting() {
        
        showingPlaceholder = false
        textColor = nil // Put the text back to the default, unmodified color
    }
}

I made my own version of the subclass of 'UITextView'.我制作了自己的“UITextView”子类版本。 I liked Sam Soffes 's idea of using the notifications, but I didn't liked the drawRect: overwrite.我喜欢Sam Soffes使用通知的想法,但我不喜欢 drawRect: overwrite。 Seems overkill to me.对我来说似乎有点矫枉过正。 I think I made a very clean implementation.我想我做了一个非常干净的实现。

You can look at my subclass here .你可以在这里查看我的子类。 A demo project is also included.还包括一个演示项目。

    - (void)textViewDidChange:(UITextView *)textView
{
    placeholderLabel.hidden = YES;
}

put a label over the textview.在文本视图上放置一个标签。

It is not possible to create placeholder in UITextView but you can generate effect like place holder by this.无法在 UITextView 中创建占位符,但您可以通过此生成类似于占位符的效果。

  - (void)viewDidLoad{      
              commentTxtView.text = @"Comment";
              commentTxtView.textColor = [UIColor lightGrayColor];
              commentTxtView.delegate = self;

     }
       - (BOOL) textViewShouldBeginEditing:(UITextView *)textView
     {
         commentTxtView.text = @"";
         commentTxtView.textColor = [UIColor blackColor];
         return YES;
     }

     -(void) textViewDidChange:(UITextView *)textView
     {

    if(commentTxtView.text.length == 0){
        commentTxtView.textColor = [UIColor lightGrayColor];
        commentTxtView.text = @"Comment";
        [commentTxtView resignFirstResponder];
    }
    }

OR you can add label in textview just like或者您可以在 textview 中添加标签,就像

       lbl = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 0.0,textView.frame.size.width - 10.0, 34.0)];


[lbl setText:kDescriptionPlaceholder];
[lbl setBackgroundColor:[UIColor clearColor]];
[lbl setTextColor:[UIColor lightGrayColor]];
textView.delegate = self;

[textView addSubview:lbl];

and set并设置

- (void)textViewDidEndEditing:(UITextView *)theTextView
{
     if (![textView hasText]) {
     lbl.hidden = NO;
}
}

- (void) textViewDidChange:(UITextView *)textView
{
    if(![textView hasText]) {
      lbl.hidden = NO;
}
else{
    lbl.hidden = YES;
 }  
}

Here's yet another way to do it, one that reproduces the slight indentation of UITextField 's placeholder:这是另一种方法,它再现了UITextField占位符的轻微缩进:

Drag a UITextField right under the UITextView so that their top left corners are aligned.UITextView正下方拖动一个UITextField ,使其左上角对齐。 Add your placeholder text to the text field.将占位符文本添加到文本字段。

In viewDidLoad, add:在 viewDidLoad 中,添加:

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

Then add:然后加:

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

Lets make it easy让我们轻松

Create one UILabel and place it on your text view(Give the text as Placeholder-set color gray-you can do all this in your xib) Now in you header file declare the UILabel and also the the textviewDelegate Now you can simply hide the label when you click on the textview创建一个 UILabel 并将其放置在您的文本视图上(将文本设置为占位符-设置颜色为灰色-您可以在 xib 中执行所有这些操作)现在在您的头文件中声明 UILabel 以及 textviewDelegate 现在您可以简单地隐藏标签当您单击文本视图时

complete code below完整代码如下

header标题

@interface ViewController :UIViewController<UITextViewDelegate>{
 }
   @property (nonatomic,strong) IBOutlet UILabel *PlceHolder_label;
   @property (nonatomic,strong) IBOutlet UITextView *TextView;

@end

implementation执行

@implementation UploadFoodImageViewController
@synthesize PlceHolder_label,TextView;

  - (void)viewDidLoad
    {
       [super viewDidLoad];
    }


 - (BOOL)textViewShouldBeginEditing:(UITextView *)textView{

       if([textView isEqual:TextView]){
            [PlceHolder_label setHidden:YES];
            [self.tabScrlVw setContentOffset:CGPointMake(0,150) animated:YES];
          }
      return YES;
    }

@end @结尾

Dont forget to connect the textView and UILabel to filesowner from xib不要忘记将 textView 和 UILabel 连接到 xib 的文件所有者

Take a look at UTPlaceholderTextView .看看UTPlaceholderTextView

This is a convenient subclass of UITextView that supports placeholder similiar to that of UITextField.这是一个方便的 UITextView 子类,它支持类似于 UITextField 的占位符。 Main peculiarities:主要特点:

  • Does not use subviews不使用子视图
  • Does not override drawRect:不覆盖 drawRect:
  • Placeholder could be of arbitrary length, and rendered just the same way as usual text占位符可以是任意长度,并以与通常文本相同的方式呈现

I read through all of these, but came up with a very short, Swift 3, solution that has worked in all of my tests.我通读了所有这些,但想出了一个非常简短的 Swift 3 解决方案,它在我的所有测试中都有效。 It could stand a little more generality, but the process is simple.它可以更具普遍性,但过程很简单。 Here's the entire thing which I call "TextViewWithPlaceholder".这是我称之为“TextViewWithPlaceholder”的全部内容。

import UIKit

class TextViewWithPlaceholder: UITextView {

    public var placeholder: String?
    public var placeholderColor = UIColor.lightGray

    private var placeholderLabel: UILabel?

    // Set up notification listener when created from a XIB or storyboard.
    // You can also set up init() functions if you plan on creating
    // these programmatically.
    override func awakeFromNib() {
        super.awakeFromNib()

        NotificationCenter.default.addObserver(self,
                                           selector: #selector(TextViewWithPlaceholder.textDidChangeHandler(notification:)),
                                           name: .UITextViewTextDidChange,
                                           object: self)

        placeholderLabel = UILabel()
        placeholderLabel?.alpha = 0.85
        placeholderLabel?.textColor = placeholderColor
    }

    // By using layoutSubviews, you can size and position the placeholder
    // more accurately. I chose to hard-code the size of the placeholder
    // but you can combine this with other techniques shown in previous replies.
    override func layoutSubviews() {
        super.layoutSubviews()

        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.text = placeholder

        placeholderLabel?.frame = CGRect(x: 6, y: 4, width: self.bounds.size.width-16, height: 24)

        if text.isEmpty {
            addSubview(placeholderLabel!)
            bringSubview(toFront: placeholderLabel!)
        } else {
            placeholderLabel?.removeFromSuperview()
        }
    }

    // Whenever the text changes, just trigger a new layout pass.
    func textDidChangeHandler(notification: Notification) {
        layoutSubviews()
    }
}

I have wrote a class in swift.我用swift写了一个类。 You can import this class whenever required.您可以在需要时导入此类。

import UIKit

public class CustomTextView: UITextView {公共类 CustomTextView: UITextView {

private struct Constants {
    static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()

private var placeholderLabelConstraints = [NSLayoutConstraint]()

@IBInspectable public var placeholder: String = "" {
    didSet {
        placeholderLabel.text = placeholder
    }
}

@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
    didSet {
        placeholderLabel.textColor = placeholderColor
    }
}

override public var font: UIFont! {
    didSet {
        placeholderLabel.font = font
    }
}

override public var textAlignment: NSTextAlignment {
    didSet {
        placeholderLabel.textAlignment = textAlignment
    }
}

override public var text: String! {
    didSet {
        textDidChange()
    }
}

override public var attributedText: NSAttributedString! {
    didSet {
        textDidChange()
    }
}

override public var textContainerInset: UIEdgeInsets {
    didSet {
        updateConstraintsForPlaceholderLabel()
    }
}

override public init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    commonInit()
}

required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    commonInit()
}

private func commonInit() {
    NSNotificationCenter.defaultCenter().addObserver(self,
                                                     selector: #selector(textDidChange),
                                                     name: UITextViewTextDidChangeNotification,
                                                     object: nil)

    placeholderLabel.font = font
    placeholderLabel.textColor = placeholderColor
    placeholderLabel.textAlignment = textAlignment
    placeholderLabel.text = placeholder
    placeholderLabel.numberOfLines = 0
    placeholderLabel.backgroundColor = UIColor.clearColor()
    placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
    addSubview(placeholderLabel)
    updateConstraintsForPlaceholderLabel()
}

private func updateConstraintsForPlaceholderLabel() {
    var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
                                                                        options: [],
                                                                        metrics: nil,
                                                                        views: ["placeholder": placeholderLabel])
    newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
                                                                     options: [],
                                                                     metrics: nil,
                                                                     views: ["placeholder": placeholderLabel])
    newConstraints.append(NSLayoutConstraint(
        item: placeholderLabel,
        attribute: .Width,
        relatedBy: .Equal,
        toItem: self,
        attribute: .Width,
        multiplier: 1.0,
        constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
        ))
    removeConstraints(placeholderLabelConstraints)
    addConstraints(newConstraints)
    placeholderLabelConstraints = newConstraints
}

@objc private func textDidChange() {
    placeholderLabel.hidden = !text.isEmpty
}

public override func layoutSubviews() {
    super.layoutSubviews()
    placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self,
                                                        name: UITextViewTextDidChangeNotification,
                                                        object: nil)
}

} }

在此处输入图像描述

I have created a swift 3 version of highest ranked answer我创建了排名最高的答案的 swift 3 版本

You just need to do subclassing of UITextView.您只需要对 UITextView 进行子类化。

import UIKit

 class UIPlaceHolderTextView: UITextView {


//MARK: - Properties
@IBInspectable var placeholder: String?
@IBInspectable var placeholderColor: UIColor?
var placeholderLabel: UILabel?


//MARK: - Initializers
override func awakeFromNib() {
    super.awakeFromNib()


}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    // Use Interface Builder User Defined Runtime Attributes to set
    // placeholder and placeholderColor in Interface Builder.
    if self.placeholder == nil {
        self.placeholder = ""
    }

    if self.placeholderColor == nil {
        self.placeholderColor = UIColor.black
    }

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

}

func textChanged(_ notification: Notification) -> Void {
    if self.placeholder?.count == 0 {
        return
    }

    UIView.animate(withDuration: 0.25) {
        if self.text.count == 0 {
            self.viewWithTag(999)?.alpha = 1
        }
        else {
            self.viewWithTag(999)?.alpha = 0
        }
    }
}

// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    super.draw(rect)

    if (self.placeholder?.count ?? 0) > 0 {
        if placeholderLabel == nil {
            placeholderLabel = UILabel.init()
            placeholderLabel?.lineBreakMode = .byWordWrapping
            placeholderLabel?.numberOfLines = 0
            placeholderLabel?.font = self.font
            placeholderLabel?.backgroundColor = self.backgroundColor
            placeholderLabel?.textColor = self.placeholderColor
            placeholderLabel?.alpha = 0
            placeholderLabel?.tag = 999
            self.addSubview(placeholderLabel!)

            placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
            placeholderLabel?.topAnchor.constraint(equalTo: self.topAnchor, constant: 7).isActive = true
            placeholderLabel?.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 4).isActive = true
            placeholderLabel?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
            placeholderLabel?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
        }

        placeholderLabel?.text = self.placeholder
        placeholderLabel?.sizeToFit()
        self.sendSubview(toBack: self.placeholderLabel!)
    }

    if self.text.count == 0 && (self.placeholder?.count ?? 0) > 0 {
        self.viewWithTag(999)?.alpha = 1
    }
 }
}

Here's a simple and clever way to get the perfect behavior.这是获得完美行为的简单而聪明的方法。

Let's borrow the placeholder from UITextField .让我们从UITextField借用占位符。

在此处输入图像描述

  1. Set up a textField and set its text transparent.设置一个 textField 并将其文本设置为透明。

     self.placeholderTextField = [[UITextField alloc] init]; /* adjust the frame to fit it in the first line of your textView */ self.placeholderTextField.frame = CGRectMake(0.0, 0.0, yourTextView.width, 30.0); self.placeholderTextField.textColor = [UIColor clearColor]; self.placeholderTextField.userInteractionEnabled = NO; self.placeholderTextField.font = yourTextView.font; self.placeholderTextField.placeholder = @"sample placeholder"; [yourTextView addSubview:self.placeholderTextField];
  2. Set textView's delegate and synchronize the textField and textView.设置 textView 的委托,同步 textField 和 textView。

     yourTextView.delegate = self;

    then然后

    - (void)textViewDidChange:(UITextView *)textView { self.placeholderTextField.text = textView.text; }
  3. That's all.就这样。

A simpler approach is to create a secondary UITextView with all of the same attributes as the original text view except for a different textColor, with constraints to ensure they stay aligned.一种更简单的方法是创建一个辅助 UITextView,它具有与原始文本视图相同的所有属性,但 textColor 不同,并具有确保它们保持对齐的约束。 Then when any characters are entered into the main text view, hide the cloned text view, otherwise show the cloned text view with some text.然后当任何字符输入到主文本视图中时,隐藏克隆文本视图,否则显示带有一些文本的克隆文本视图。

This can be achieved in several ways, but a relatively clean way would be to subclass UITextView and keep all of this logic within the subclass.这可以通过多种方式实现,但一种相对简洁的方式是将 UITextView 子类化并将所有这些逻辑保留在子类中。

So, subclass UITextView and allow it to create it's place holder view lazily:因此,继承 UITextView 并允许它懒惰地创建它的占位符视图:

Interface file:接口文件:

@interface FOOTextView : UITextView <UITextViewDelegate>

@property (nonatomic, copy) NSString *placeholderText;

- (void)checkPlaceholder;

@end

Implementation file:实现文件:

#import "FOOTextView.h"

@interface FOOTextView ()

@property (nonatomic, strong) UITextView *placeholderTextView;

@end

@implementation FOOTextView

- (void)checkPlaceholder {
    // Hide the placeholder text view if we've got any text
    self.placeholderTextView.hidden = (self.text.length > 0 || self.attributedText.length > 0);
}

- (void)setPlaceholderText:(NSString *)placeholderText {
    _placeholderText = [placeholderText copy];

    // Setup the placeholder text view if we haven't already
    [self setupPlaceholderTextView];

    // Apply the placeholder text to the placeholder text view
    self.placeholderTextView.text = placeholderText;
}

- (void)setupPlaceholderTextView {
    if (!self.placeholderTextView) {

        // Setup the place holder text view, duplicating our visual setup
        self.placeholderTextView = [[UITextView alloc] initWithFrame:CGRectZero];
        self.placeholderTextView.translatesAutoresizingMaskIntoConstraints = NO;
        self.placeholderTextView.textColor = self.placeholderTextColor ? self.placeholderTextColor : [UIColor colorWithRed:199.f/255.f green:199.f/255.f blue:205.f/255.f alpha:1.f];
        self.placeholderTextView.userInteractionEnabled = NO;
        self.placeholderTextView.font = self.font;
        self.placeholderTextView.textAlignment = self.textAlignment;
        self.placeholderTextView.backgroundColor = self.backgroundColor;
        self.placeholderTextView.editable = NO;

        // Our background color must be clear for the placeholder text view to show through
        self.backgroundColor = [UIColor clearColor];

        // Insert the placeholder text view into our superview, below ourself so it shows through
        [self.superview insertSubview:self.placeholderTextView belowSubview:self];

        // Setup constraints to ensure the placeholder text view stays aligned with us
        NSLayoutConstraint *constraintCenterX = [NSLayoutConstraint constraintWithItem:self.placeholderTextView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f];
        NSLayoutConstraint *constraintCenterY = [NSLayoutConstraint constraintWithItem:self.placeholderTextView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:0.f];
        NSLayoutConstraint *constraintWidth = [NSLayoutConstraint constraintWithItem:self.placeholderTextView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.f constant:0.f];
        NSLayoutConstraint *constraintHeight = [NSLayoutConstraint constraintWithItem:self.placeholderTextView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f];
        NSArray *constraints = @[constraintCenterX, constraintCenterY, constraintWidth, constraintHeight];
        [self.superview addConstraints:constraints];

    }
}

- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor {
    _placeholderTextColor = placeholderTextColor;
    self.placeholderTextView.textColor = _placeholderTextColor;
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    // We don't want a background color ourselves, instead we want our placeholder text view to have the desired background color
    [self.placeholderTextView setBackgroundColor:backgroundColor];
}

- (void)removeFromSuperview {
    // Ensure we also remove our placeholder text view
    [self.placeholderTextView removeFromSuperview];
    self.placeholderTextView = nil;
    [super removeFromSuperview];
}

#pragma mark - Text View Delegation 
- (void)textViewDidChange:(UITextView *)textView {
    [self checkPlaceholder];
}

@end

Using the above class, if you set an instance of FOOTextView's delegate to itself, everything will work out of the box:使用上面的类,如果你将 FOOTextView 的委托实例设置为自身,一切都会开箱即用:

FOOTextView *myTextView = ...
myTextView.placeholderText = @"What's on your mind?";
myTextView.placeholderTextColor = [UIColor lightGrayColor];
myTextView.delegate = myTextView;

If you'd like another object to take over as the delegate, then you just need to call the text view's checkPlaceholder method in the textViewDidChange: delegate method, eg;如果您希望另一个对象接管作为委托,那么您只需要在 textViewDidChange: 委托方法中调用文本视图的 checkPlaceholder 方法,例如;

FOOTextView *myTextView = ...
myTextView.placeholderText = @"What's on your mind?";
myTextView.placeholderTextColor = [UIColor lightGrayColor];
myTextView.delegate = self;
self.myTextView = myTextView;

- (void)textViewDidChange:(UITextView *)textView {
    // Call the checkPlaceholder method to update the visuals
    [self.myTextView checkPlaceholder];
}

I know there are already a lot of answers to this one, but I didn't really find any of them sufficient (at least in Swift).我知道这个问题已经有很多答案了,但我并没有真正找到足够的答案(至少在 Swift 中)。 I needed the "placeholder" functionality of the UITextField in my UITextView (I wanted the exact behavior, including text display attributes, animations, etc, and didn't want to have to maintain this over time).我需要UITextViewUITextField的“占位符”功能(我想要确切的行为,包括文本显示属性、动画等,并且不想随着时间的推移维护它)。 I also wanted a solution that provided the same exact border as a UITextField (not an approximated one that looks sort of like it looks right now, but one that looks exactly like it and will always look exactly like it).我还想要一个解决方案,它提供与UITextField相同的确切边框(不是一个看起来有点像现在的近似值,而是一个看起来完全像它并且总是看起来完全像它的近似值)。 So while I was not initially a fan of jamming an extra control into the mix, it seemed that in order to meet my goals I had to use an actual UITextField and let it do the work.因此,虽然我最初并不喜欢在混音中加入额外的控件,但似乎为了实现我的目标,我必须使用实际的UITextField并让它完成工作。

This solution handles positioning the placeholder and keeping the font in sync between the two controls so that the placeholder text is the exact font and position as text entered into the control (something a lot of the other solutions don't address).此解决方案处理占位符的定位并在两个控件之间保持字体同步,以便占位符文本与输入到控件中的文本完全相同(许多其他解决方案未解决的问题)。

// This class is necessary to support "inset" (required to position placeholder 
// appropriately in TextView)
//
class TextField: UITextField
{
    var inset: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0);

    override func textRectForBounds(bounds: CGRect) -> CGRect
    {
        return UIEdgeInsetsInsetRect(bounds, inset);
    }

    override func placeholderRectForBounds(bounds: CGRect) -> CGRect
    {
        return UIEdgeInsetsInsetRect(bounds, inset);
    }
}

// This class implements a UITextView that has a UITextField behind it, where the 
// UITextField provides the border and the placeholder text functionality (so that the
// TextView looks and works like a UITextField).
//
class TextView : UITextView, UITextViewDelegate
{
    var textField = TextField();

    required init?(coder: NSCoder)
    {
        super.init(coder: coder);
    }

    override init(frame: CGRect, textContainer: NSTextContainer?)
    {
        super.init(frame: frame, textContainer: textContainer);

        self.delegate = self;

        // Create a background TextField with clear (invisible) text and disabled
        self.textField.borderStyle = UITextBorderStyle.RoundedRect;
        self.textField.textColor = UIColor.clearColor();
        self.textField.userInteractionEnabled = false;

        // Align the background TextView to where text appears in the TextField, so
        // that any placeholder will be in the correct position.
        self.textField.contentVerticalAlignment = UIControlContentVerticalAlignment.Top;
        self.textField.inset = UIEdgeInsets(
            top: self.textContainerInset.top,
            left: self.textContainerInset.left + self.textContainer.lineFragmentPadding,
            bottom: self.textContainerInset.bottom,
            right: self.textContainerInset.right
        );

        // The background TextField should use the same font (for the placeholder)
        self.textField.font = self.font;

        self.addSubview(textField);
        self.sendSubviewToBack(textField);
    }

    convenience init()
    {
        self.init(frame: CGRectZero, textContainer: nil)
    }

    override var font: UIFont?
    {
        didSet
        {
            // Keep the font of the TextView and background textField in sync
            self.textField.font = self.font;
        }
    }

    var placeholder: String? = nil
    {
        didSet
        {
            self.textField.placeholder = self.placeholder;
        }
    }

    override func layoutSubviews()
    {
        super.layoutSubviews()
        // Do not scroll the background textView
        self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
    }

    // UITextViewDelegate - Note: If you replace delegate, your delegate must call this
    func scrollViewDidScroll(scrollView: UIScrollView)
    {
        // Do not scroll the background textView
        self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
    }

    // UITextViewDelegate - Note: If you replace delegate, your delegate must call this
    func textViewDidChange(textView: UITextView)
    {
        // Updating the text in the background textView will cause the placeholder to 
        // appear/disappear (including any animations of that behavior - since the
        // textView is doing this itself).
        self.textField.text = self.text;
    }
}

One more simple answer for this, using CATextLayer.一个更简单的答案,使用CATextLayer.

Add CATextLayer to UITextView's layer.CATextLayer添加到UITextView's图层。 With UITextViewDelegate methods, simply change colour of CATextLayer .使用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)
        }
    }

}

I created an instance variable to check whether I'll show the placeholder or not:我创建了一个实例变量来检查我是否会显示占位符:

BOOL showPlaceHolder;
UITextView * textView; // and also the textView

On viewDidLoad I set:在 viewDidLoad 我设置:

[self setPlaceHolder]; 

Here's what this does:这是这样做的:

- (void)setPlaceholder
{
    textView.text = NSLocalizedString(@"Type your question here", @"placeholder");
    textView.textColor = [UIColor lightGrayColor];
    self.showPlaceHolder = YES; //we save the state so it won't disappear in case you want to re-edit it
}

I also created a button to resign the keyboard.我还创建了一个按钮来退出键盘。 You don't have to do this but the cool thing here is that the placeholder is shown again if nothing was entered您不必这样做,但这里很酷的是,如果未输入任何内容,则会再次显示占位符

- (void)textViewDidBeginEditing:(UITextView *)txtView 
{
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(resignKeyboard)];
    if (self.showPlaceHolder == YES) 
    {
        textView.textColor = [UIColor blackColor];
        textView.text = @"";
        self.showPlaceHolder = NO;
    }
}

- (void)resignKeyboard 
{
    [textView resignFirstResponder];
    //here if you created a button like I did to resign the keyboard, you should hide it
    if (textView.text.length == 0) {
        [self setPlaceholder];
    }       
}

OK my ansewer is a bit different I create a small class to do it for you.好的,我的答案有点不同,我创建了一个小班来为你做这件事。

TextViewShader.m file TextViewShader.m 文件

#import "TextViewShader.h"

@implementation TextViewShader
-(id)initWithShadedTextView:(NSString *)text textViewToShade:(UITextView *)textview {
    self = [super initWithFrame:textview.frame];
    if (self) {
        if (shadeLabel==nil)
        {
            shadeLabel= [[UILabel alloc]initWithFrame:CGRectMake(10, 0, textview.frame.size.width, 30)];


    }
    shadeLabel.text =text;// @"Enter Your Support Request";
    shadeLabel.textColor = [UIColor lightGrayColor];
    [textview setDelegate: self];
    [textview addSubview:shadeLabel];
}
return self;
}

-(void)textViewDidChange:(UITextView *)textView{
        if (textView.text.length==0)
        {
            shadeLabel.hidden=false; 
        }
        else
        {
            shadeLabel.hidden=true;
        }

}

@end

TextViewShader.h file TextViewShader.h 文件

#import <UIKit/UIKit.h>

@interface TextViewShader : UIView<UITextViewDelegate>{
    UILabel *shadeLabel;

}
-(id)initWithShadedTextView:(NSString *)text textViewToShade:(UITextView *)textview ;
@end

this is the simple one line of code usage (dont forget to add #import "TextViewShader.h")这是简单的一行代码用法(不要忘记添加#import“TextViewShader.h”)

 TextViewShader* shader = [[TextViewShader alloc]initWithShadedTextView:@"Enter Your Support Request" textViewToShade: youruitextviewToshade];

have fun :)玩得开心 :)

- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
    //NSLog(@"textViewShouldBeginEditing");
    if( [tvComment.text isEqualToString:@"Comment"] && [tvComment.textColor isEqual:[UIColor lightGrayColor]] ){
        tvComment.text = @"";
        tvComment.textColor = [UIColor blackColor];
    }
    return YES;
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification{
    //NSLog(@"keyboardWillBeHidden");

    //Manage comment field placeholdertext
    if(tvComment.text.length == 0){
        tvComment.textColor = [UIColor lightGrayColor];
        tvComment.text = @"Comment";
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    tvComment.textColor = [UIColor lightGrayColor];
}

TVComment is the property that holds the textView in question. TVComment 是保存有问题的 textView 的属性。 This will do the trick.这会成功的。

I've written a cleaner implementation after trying some of the proposed approaches and posted it to Github .在尝试了一些建议的方法并将其发布到 Github之后,我编写了一个更简洁的实现。 Pull requests and issues are welcome.欢迎请求请求和问题。

Some key improvements that versus other approaches presented here:与此处介绍的其他方法相比,一些关键改进:

  • Doesn't allocate a UILabel inside drawRect: .不在drawRect:内分配 UILabel。 (Please don't ever do that.) (请永远不要那样做。)
  • Doesn't compare the text view's current text to a desired placeholder to swap colors.不将文本视图的当前文本与所需的占位符进行比较以交换颜色。
  • Hides the placeholder while dictation is active (like UITextField ).在听写处于活动状态时隐藏占位符(如UITextField )。

In .h class在 .h 类中

@interface RateCommentViewController : UIViewController<UITextViewDelegate>{IBoutlet UITextview *commentTxtView;}

In .m class在 .m 类中

- (void)viewDidLoad{      
    commentTxtView.text = @"Comment";
    commentTxtView.textColor = [UIColor lightGrayColor];
    commentTxtView.delegate = self;
}

- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
    commentTxtView.text = @"";
    commentTxtView.textColor = [UIColor blackColor];
    return YES;
}

-(void) textViewDidChange:(UITextView *)textView
{
    if(commentTxtView.text.length == 0){
        commentTxtView.textColor = [UIColor lightGrayColor];
        commentTxtView.text = @"Comment";
        [commentTxtView resignFirstResponder];
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];

    self.textViewEmpty = YES;

    // Text view
    self.textView = [[UITextView alloc] init];
    self.textView.translatesAutoresizingMaskIntoConstraints = NO; // For AutoLayout
    self.textView.delegate = self;
    self.textView.textColor = [UIColor grayColor];
    self.textView.text = @"Placeholder";

    // Add subview and constraints
}

#pragma mark - UITextView

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
    if (self.isTextViewEmpty) {
        textView.textColor = [UIColor blackColor];
        textView.text = @"";
    }

    return YES;
}

- (void)textViewDidChange:(UITextView *)textView {
    if (textView.text.length > 0) {
        self.textViewEmpty = NO;
    } else {
        self.textViewEmpty = YES;
    }
}

- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
    if (self.isTextViewEmpty) {
        textView.textColor = [UIColor lightGrayColor];
        textView.text = placeholderText;
    }

    return YES;
}

Simulating Native Placeholders模拟原生占位符


A common gripe is that iOS doesn't provide a native placeholder feature for textviews.一个常见的抱怨是 iOS 没有为文本视图提供原生占位符功能。 The UITextView extension below attempts to address that concern by offering the convenience one would expect from a native feature, requiring only one line of code to add a placeholder to a textview instance.下面的UITextView扩展试图通过提供人们期望从本机功能获得的便利来解决这个问题,只需要一行代码即可将占位符添加到 textview 实例。

The downside of this solution is, because it daisy chains delegate calls, it is vulnerable to (unlikely) changes to the UITextViewDelegate protocol in an iOS update.这个解决方案的缺点是,因为它菊花链委托调用,它很容易受到(不太可能)在 iOS 更新中对 UITextViewDelegate 协议的更改。 Specifically, if iOS adds new protocol methods and you implement any of them in the delegate for a text view with a placeholder, those methods won't be called unless you've also updated the extension to forward those calls.具体来说,如果 iOS 添加了新的协议方法,并且您在代理中为带有占位符的文本视图实现了其中的任何方法,则不会调用这些方法,除非您更新了扩展以转发这些调用。

Alternatively, the Inline Placeholder answer is a rock-solid and about as simple as can be.或者,内联占位符答案是坚如磐石的,并且尽可能简单。


Usage examples:使用示例:


• If the text view gaining the placeholder doesn't use a UITextViewDelegate : • 如果获得占位符的文本视图使用UITextViewDelegate

    /* Swift 3 */

    class NoteViewController : UIViewController {
        @IBOutlet weak var noteView: UITextView!
        override func viewDidLoad() {
            noteView.addPlaceholder("Enter some text...",  color: UIColor.lightGray)
        }
    }

-- OR -- - 或者 -

• If the text view gaining the placeholder does use a UITextViewDelegate : • 如果获得占位符的文本视图确实使用了UITextViewDelegate

    /* Swift 3 */

    class NoteViewController : UIViewController, UITextViewDelegate {
        @IBOutlet weak var noteView: UITextView!
        override func viewDidLoad() {
            noteView.addPlaceholder("Phone #", color: UIColor.lightGray, delegate: self)
        }
    }

Implementation ( UITextView extension):实现( UITextView扩展):


/* Swift 3 */

extension UITextView: UITextViewDelegate
{

    func addPlaceholder(_ placeholderText : String, 
                      color : UIColor? = UIColor.lightGray,
                      delegate : UITextViewDelegate? = nil) {

        self.delegate = self             // Make receiving textview instance a delegate
        let placeholder = UITextView()   // Need delegate storage ULabel doesn't provide
        placeholder.isUserInteractionEnabled = false  //... so we *simulate* UILabel
        self.addSubview(placeholder)     // Add to text view instance's view tree               
        placeholder.sizeToFit()          // Constrain to fit inside parent text view
        placeholder.tintColor = UIColor.clear // Unused in textviews. Can host our 'tag'
        placeholder.frame.origin = CGPoint(x: 5, y: 0) // Don't cover I-beam cursor
        placeholder.delegate = delegate  // Use as cache for caller's delegate 
        placeholder.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
        placeholder.text = placeholderText
        placeholder.textColor = color
    }

      
    func findPlaceholder() -> UITextView? { // find placeholder by its tag 
        for subview in self.subviews {
            if let textview = subview as? UITextView {
                if textview.tintColor == UIColor.clear { // sneaky tagging scheme
                    return textview
                }
            }
        }
        return nil
    }
     
    /* 
     * Safely daisychain to caller delegate methods as appropriate...
     */

    public func textViewDidChange(_ textView: UITextView) { // ←  need this delegate method
        if let placeholder = findPlaceholder() {
            placeholder.isHidden = !self.text.isEmpty // ← ... to do this
            placeholder.delegate?.textViewDidChange?(textView)
        } 
    }

    /* 
     * Since we're becoming a delegate on behalf of this placeholder-enabled
     * text view instance, we must forward *all* that protocol's activity expected
     * by the instance, not just the particular optional protocol method we need to
     * intercept, above.
     */

    public func textViewDidEndEditing(_ textView: UITextView) {
        if let placeholder = findPlaceholder() {
            placeholder.delegate?.textViewDidEndEditing?(textView)
        } 
    }

    public func textViewDidBeginEditing(_ textView: UITextView) {
        if let placeholder = findPlaceholder() {
            placeholder.delegate?.textViewDidBeginEditing?(textView)
        } 
    }

    public  func textViewDidChangeSelection(_ textView: UITextView) {
        if let placeholder = findPlaceholder() {
            placeholder.delegate?.textViewDidChangeSelection?(textView)
        } 
    }

    public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
        if let placeholder = findPlaceholder() {
            guard let retval = placeholder.delegate?.textViewShouldEndEditing?(textView) else {
                return true
            }
            return retval
        }
        return true
    }

    public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        if let placeholder = findPlaceholder() {
            guard let retval = placeholder.delegate?.textViewShouldBeginEditing?(textView) else {
                return true
            }
            return retval
        } 
        return true
    }

    public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if let placeholder = findPlaceholder() {
            guard let retval = placeholder.delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) else {
                return true
            }
            return retval
        } 
        return true
    }

    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        if let placeholder = findPlaceholder() {
                guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction:
                    interaction) else {
                        return true
            }
            return retval
        }
        return true
    }

    public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        if let placeholder = findPlaceholder() {
            guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) else {
                return true
            }
            return retval
        }
        return true
    }
}

1. As an extension of an essential iOS class like UITextView, it's important to know that this code has no interaction with any textviews that don't activate a placeholder, eg textview instances that haven't been initialized with a call addPlaceholder() 1.作为 UITextView 等基本 iOS 类的扩展,重要的是要知道此代码与任何不激活占位符的 textview没有交互,例如尚未通过调用addPlaceholder()初始化的 textview 实例

2. Placeholder-enabled text views transparently become a UITextViewDelegate to track character count, in order to control placeholder visibility. 2.启用占位符的文本视图透明地变成一个UITextViewDelegate来跟踪字符数,以控制占位符的可见性。 If a delegate is passed to addPlaceholder() , this code daisy-chains (ie forwards) delegate callbacks to that delegate.如果将委托传递给addPlaceholder() ,则此代码菊花链(即转发)委托回调到该委托。

3. The author is investigating ways to inspect the UITextViewDelegate protocol and proxy it automatically without having to hardcode each method. 3.作者正在研究检查UITextViewDelegate协议并自动代理它的方法,而无需对每个方法进行硬编码。 That would inoculate the code from method signature changes and new methods being added to the protocol.这将使代码免受方法签名更改和添加到协议中的新方法的影响。

Another solution另一种解决方案

import UIKit

protocol PlaceholderTextViewDelegate: class {

    func placeholderTextViewDidChangeText(_ text: String)
    func placeholderTextViewDidEndEditing(_ text: String)
}

final class PlaceholderTextView: UITextView {

    weak var notifier: PlaceholderTextViewDelegate?
    var ignoreEnterAction: Bool = true

    var placeholder: String? {
        didSet {
            text = placeholder
            selectedRange = NSRange(location: 0, length: 0)
        }
    }

    var placeholderColor = UIColor.lightGray {
        didSet {
            if text == placeholder {
                textColor = placeholderColor
            }
        }
    }
    var normalTextColor = UIColor.lightGray

    var placeholderFont = UIFont.sfProRegular(28)

    fileprivate var placeholderLabel: UILabel?

    // MARK: - LifeCycle

    override var text: String? {
        didSet {
            if text == placeholder {
                textColor = placeholderColor
            } else {
                textColor = normalTextColor
            }
        }
    }

    init() {
        super.init(frame: CGRect.zero, textContainer: nil)
        awakeFromNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        self.delegate = self
    }
}

extension PlaceholderTextView: UITextViewDelegate {

    // MARK: - UITextViewDelegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

        if text == "" && textView.text == placeholder {
            return false
        }

        if let placeholder = placeholder,
            textView.text == placeholder,
            range.location <= placeholder.count {
            textView.text = ""
        }

        if ignoreEnterAction && text == "\n" {
            textView.resignFirstResponder()
            return false
        }
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        if let placeholder = placeholder {
            textView.text = textView.text.replacingOccurrences(of: placeholder, with: "")
        }

        if let placeholder = placeholder,
            text?.isEmpty == true {
            text = placeholder
            textColor = placeholderColor

            selectedRange = NSRange(location: 0, length: 0)
        } else {
            textColor = normalTextColor
        }

        notifier?.placeholderTextViewDidChangeText(textView.text)
    }

    func textViewDidChangeSelection(_ textView: UITextView) {
        if let placeholder = placeholder,
            text == placeholder {
            selectedRange = NSRange(location: 0, length: 0)
        }
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        notifier?.placeholderTextViewDidEndEditing(textView.text)

        if let placeholder = placeholder,
            text?.isEmpty == true {
            text = placeholder
            textColor = placeholderColor
            selectedRange = NSRange(location: 0, length: 0)
        } else {
            textColor = normalTextColor
        }
    }
}

result:结果:

在此处输入图像描述

TextView PlaceHolder In swift TextView PlaceHolder 在 swift

import UIKit

@IBDesignable
open class KMPlaceholderTextView: UITextView {

    private struct Constants {
        static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
    }

    public let placeholderLabel: UILabel = UILabel()

    private var placeholderLabelConstraints = [NSLayoutConstraint]()

    @IBInspectable open var placeholder: String = "" {
        didSet {
            placeholderLabel.text = placeholder
        }
    }

    @IBInspectable open var placeholderColor: UIColor = KMPlaceholderTextView.Constants.defaultiOSPlaceholderColor {
        didSet {
            placeholderLabel.textColor = placeholderColor
        }
    }

    override open var font: UIFont! {
        didSet {
            if placeholderFont == nil {
                placeholderLabel.font = font
            }
        }
    }

    open var placeholderFont: UIFont? {
        didSet {
            let font = (placeholderFont != nil) ? placeholderFont : self.font
            placeholderLabel.font = font
        }
    }

    override open var textAlignment: NSTextAlignment {
        didSet {
            placeholderLabel.textAlignment = textAlignment
        }
    }

    override open var text: String! {
        didSet {
            textDidChange()
        }
    }

    override open var attributedText: NSAttributedString! {
        didSet {
            textDidChange()
        }
    }

    override open var textContainerInset: UIEdgeInsets {
        didSet {
            updateConstraintsForPlaceholderLabel()
        }
    }

    override public init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        #if swift(>=4.2)
        let notificationName = UITextView.textDidChangeNotification
        #else
        let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
        #endif

        NotificationCenter.default.addObserver(self,
                                               selector: #selector(textDidChange),
                                               name: notificationName,
                                               object: nil)

        placeholderLabel.font = font
        placeholderLabel.textColor = placeholderColor
        placeholderLabel.textAlignment = textAlignment
        placeholderLabel.text = placeholder
        placeholderLabel.numberOfLines = 0
        placeholderLabel.backgroundColor = UIColor.clear
        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(placeholderLabel)
        updateConstraintsForPlaceholderLabel()
    }

    private func updateConstraintsForPlaceholderLabel() {
        var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
            options: [],
            metrics: nil,
            views: ["placeholder": placeholderLabel])
        newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
            options: [],
            metrics: nil,
            views: ["placeholder": placeholderLabel])
        newConstraints.append(NSLayoutConstraint(
            item: placeholderLabel,
            attribute: .width,
            relatedBy: .equal,
            toItem: self,
            attribute: .width,
            multiplier: 1.0,
            constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
        ))
        removeConstraints(placeholderLabelConstraints)
        addConstraints(newConstraints)
        placeholderLabelConstraints = newConstraints
    }

    @objc private func textDidChange() {
        placeholderLabel.isHidden = !text.isEmpty
        self.layoutIfNeeded()
    }

    open override func layoutSubviews() {
        super.layoutSubviews()
        placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
    }

    deinit {
        #if swift(>=4.2)
        let notificationName = UITextView.textDidChangeNotification
        #else
        let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
        #endif

        NotificationCenter.default.removeObserver(self,
                                                  name: notificationName,
                                                  object: nil)
    }

}

Usage用法

在此处输入图像描述

If your looking for a simple way to achieve this , try my approach:如果您正在寻找一种简单的方法来实现这一点,请尝试我的方法:

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
    if ([[textView text] isEqualToString:PLACE_HOLDER_TEXT]) {
          textView.text = @"";
          textView.textColor = [UIColor blackColor];
    }

    return YES;
}

-(BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    if ([[textView text] length] == 0) {
        textView.text = PLACE_HOLDER_TEXT;
        textView.textColor = [UIColor lightGrayColor];
    }
    return YES;
}

Yes, that's it PLACE_HOLDER_TEXT is an NSString containing your placeholder是的,就是这样PLACE_HOLDER_TEXT是一个NSString包含你的占位符

I found own solution我找到了自己的解决方案

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:PLACEHOLDER_TEXT])
    {
        textView.textColor = [UIColor lightGrayColor];
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           textView.selectedRange = NSMakeRange(0, 0);
                       });
    }
    else
    {
        textView.textColor = [UIColor blackColor];
    }

    [textView becomeFirstResponder];
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:@""])
    {
        textView.text = PLACEHOLDER_TEXT;
        textView.textColor = [UIColor lightGrayColor];
    }

    [textView resignFirstResponder];
}

- (BOOL)textView:(UITextView *)textView
shouldChangeTextInRange:(NSRange)range
 replacementText:(NSString *)text
{
    if (range.location == 0 && range.length == [[textView text] length] && [text isEqualToString:@""])
    {
        textView.text = PLACEHOLDER_TEXT;
        textView.textColor = [UIColor lightGrayColor];

        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           textView.selectedRange = NSMakeRange(0, 0);
                       });

        return NO;
    }

    if ([textView.text isEqualToString:PLACEHOLDER_TEXT])
    {
        textView.text = @"";
        textView.textColor = [UIColor blackColor];
    }

    return YES;
}

Yet another answer:还有一个答案:

https://github.com/gcamp/GCPlaceholderTextView https://github.com/gcamp/GCPlaceholderTextView

Change Class of UITextView in IB to GCPlaceholderTextView and set the placeholder property将 IB 中的UITextView类更改为GCPlaceholderTextView并设置placeholder属性

Swift 3.1斯威夫特 3.1

After trying all swift answers, this answer would have saved me 3 hours of research.在尝试了所有快速的答案之后,这个答案将为我节省 3 个小时的研究时间。 Hope this helps.希望这可以帮助。

  1. Make sure your textField (no matter the custom name you have) is pointing to its delegate in Storyboard and has an @IBOutlet with yourCustomTextField确保您的 textField(无论您有什么自定义名称)指向它在 Storyboard 中的委托,并且有一个带有 yourCustomTextField 的yourCustomTextField

  2. Add to viewDidLoad() the following, it will appear when loading the view:将以下内容添加到viewDidLoad()中,它会在加载视图时出现:

Show me what looks to be a placeholder:告诉我什么看起来是占位符:

yourCustomTextField = "Start typing..." 
yourCustomTextField.textColor = .lightGray
  1. Outside the viewDidLoad but inside the same class add the following declarations: UIViewController, UITextViewDelegate, UINavigationControllerDelegate在 viewDidLoad 之外但在同一个类中添加以下声明: UIViewController, UITextViewDelegate, UINavigationControllerDelegate

This code will make yourCustomTextField go away when typing in the textField:在输入文本字段时,此代码将使 yourCustomTextField 消失:

func textViewDidBeginEditing (_ textView: UITextView) { 

    if (textView.text == "Start typing...") {

        textView.text = ""
        textView.textColor = .black
    }

    textView.becomeFirstResponder()
}

func textViewDidEndEditing(_ textView: UITextView) {
    if (textView.text == "") {

        textView.text = "Start typing..."
        textView.textColor = .lightGray
    }

    textView.resignFirstResponder()
}

Here is code for swift 3.1这是 swift 3.1 的代码

Original code by Jason George in first answer. Jason George 在第一个答案中的原始代码。

Don't forget to set your custom class for TextView in interface builder to UIPlaceHolderTextView and then set placeholder and placeHolder properties.不要忘记在界面构建器中将 TextView 的自定义类设置为 UIPlaceHolderTextView,然后设置 placeholder 和 placeHolder 属性。

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)
}
}

I followed the code from this link .我按照此链接中的代码进行操作 There are only 7 easy steps.只有7个简单的步骤。 It adds a UILabel to the textView and hides/shows the label when text is entered or removed from the textView via the textView's textViewDidChangeSelection(_ textView: UITextView) delegate method.它向 textView 添加一个 UILabel 并在通过 textView 的textViewDidChangeSelection(_ textView: UITextView)委托方法从 textView 输入或删除文本时隐藏/显示标签。 I put the steps in the comments above the code.我将这些步骤放在代码上方的注释中。

// 1. make sure to include the UITextViewDelegate
class YourClass: UITextViewDelegate {

    @IBOutlet weak var textView : UITextView!

    // 2. create placeholder textLabel
    let placeHolderTextLabel: UILabel = {
        let placeholderLabel = UILabel()
        placeholderLabel.text = "Placeholder text..."
        placeholderLabel.sizeToFit()
        placeholderLabel.textColor = UIColor.lightGray
        return placeholderLabel
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 3. set textView delegate
        textView.delegate = self

        configurePlaceholderTextLabel()
    }


    func configurePlaceholderTextLabel() {

        // 4. add placeholder label to textView, set it's frame and font
        textView.addSubview(placeHolderTextLabel)
        placeHolderTextLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
        placeHolderTextLabel.font = UIFont.systemFont(ofSize: (textView.font?.pointSize)!)

        // 5. decide wether the placeHolderTextLabel is hidden or not depending on if there is or isn't text inside the textView
        placeHolderTextLabel.isHidden = !textView.text.isEmpty

    }

    // 6. implement textView delegate method to update the placeHolderTextLabel when the text is changed
    func textViewDidChangeSelection(_ textView: UITextView) {

        // 7. decide wether the placeHolderTextLabel is hidden or not depending on if there is or isn't text inside the textView when text in textView is changed
        placeHolderTextLabel.isHidden = !textView.text.isEmpty
    }

}

Simpler and still accounting for some text entered by the user at some point更简单,并且仍然考虑用户在某些时候输入的一些文本

BOOL placeHolderTextVisible;

on viewDidLoad, set it to YES (or DidMoveToSuperview, or awakeFromNib)在 viewDidLoad 上,将其设置为 YES(或 DidMoveToSuperview,或 awakeFromNib)

then, on - (BOOL) textView:(UITextView*)textView shouldBeginEditing然后,在 - (BOOL) textView:(UITextView*)textView shouldBeginEditing

    - (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
{
   if (placeHolderTextVisible) {
    placeHolderTextVisible = NO;
    textView.text = @"";
   }
 return YES;
}

You can just set a label on the textview.您可以在 textview 上设置一个标签。

MyUITextView.h MyUITextView.h

@interface MyUITextView : UITextView {
    UILabel* _placeholderLabel;
}

@property(nonatomic, assign)NSString *placeholder;

MyUITextView.m MyUITextView.m

@implementation MyUITextView

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // Create placeholder
        viewFrame = CGRectMake(0, 0, frame.size.width, 15);
        _placeholderLabel = [[UILabel alloc] initWithFrame:viewFrame];
        _placeholderLabel.textColor = [UIColor lightGrayColor];
        [self addSubview:_placeholderLabel];

        // Add text changed notification 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
    }
    return self;
}

- (void)setPlaceholder:(NSString *)placeholder {
    _placeholderLabel.text = placeholder;
}

- (NSString*)placeholder {
    return _placeholderLabel.text;
}

#pragma mark UITextViewTextDidChangeNotification

- (void)textChanged:(NSNotification *)notification {
    _placeholderLabel.hidden = ([self.text lenght] == 0);
}

@end

Jason's answer will looks a little bit off in iOS7, fix it by adjust the offset of _placeHolderLabel: Jason 的答案在 iOS7 中看起来会有点偏离,通过调整 _placeHolderLabel 的偏移量来修复它:

- (void)drawRect:(CGRect)rect
{
    if( [[self placeholder] length] > 0 )
    {
        if (_placeHolderLabel == nil )
        {
            if ([[UIDevice currentDevice].systemVersion floatValue] >= 7)
                _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(4,8,self.bounds.size.width - 8,0)];
            else
                _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8,8,self.bounds.size.width - 16,0)];
            _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping;
            _placeHolderLabel.numberOfLines = 0;
            _placeHolderLabel.font = self.font;
            _placeHolderLabel.backgroundColor = [UIColor clearColor];
            _placeHolderLabel.textColor = self.placeholderColor;
            _placeHolderLabel.alpha = 0;
            _placeHolderLabel.tag = 999;
            [self addSubview:_placeHolderLabel];
        }

        _placeHolderLabel.text = self.placeholder;
        [_placeHolderLabel sizeToFit];
        [self sendSubviewToBack:_placeHolderLabel];
    }

    if( [[self text] length] == 0 && [[self placeholder] length] > 0 )
    {
        [[self viewWithTag:999] setAlpha:1];
    }

    [super drawRect:rect];
}

I've just found that as of iOS 10 you can now actually cast an UITextView to a method as an UITextField and set inside the method the placeholder.我刚刚发现,从iOS 10开始,您现在实际上可以将UITextView作为UITextField转换为方法,并在方法中设置占位符。 Just tried and it works without having to subclass UITextView .刚刚尝试过,它无需UITextView就可以工作。

This is an example of what worked for me:这是一个对我有用的例子:

-(void)customizeTextField:(UITextField *)textField placeholder:(NSString *)pText withColor:(UIColor *)pTextColor{

        textField.attributedPlaceholder = [[NSAttributedString alloc]
                                          initWithString:pText
                                          attributes:@{NSForegroundColorAttributeName:pTextColor}];
    }

And to use it for a UITextView you just have to pass it to the method using a cast like this:要将它用于UITextView ,您只需使用这样的强制转换将其传递给方法:

[self customizeTextField:(UITextField*)_myTextView placeholder:@"Placeholder" withColor:[UIColor blackColor]];

NB: After a test I've found out that the solution works fine also on iOS9.x but causes a crash on iOS8.x注意:经过测试,我发现该解决方案在iOS9.x iOS8.x会导致崩溃

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.在查看(并尝试)大多数针对 UITextView 这个看似明显但缺失的功能的建议解决方案之后,我发现的“最佳”最接近的是 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.但我不喜欢求助于一个全新的子类[我更喜欢插入式类别来添加这样简单的功能],也不喜欢它拦截 UITextViewDelegate 方法,这可能会弄乱你现有的 UITextView 处理代码。 So here's my take on a drop-in category that'll work on any existing UITextView instance...所以这是我对适用于任何现有 UITextView 实例的插入类别的看法......

#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

Its simple to use, just the obvious它使用简单,显而易见

UITextView *myTextView = UITextView.new;
...
myTextView.placeholder = @"enter text here";

It works by adding a UITextField - in the right location - behind your UITextView, and exploiting it's placeholder instead (hence you dont have to worry about getting the color right, etc), then listening for notifications whenever your UITextView is changed to show/hide this UITextField (and hence it doesnt interfere with your existing UITextViewDelegate calls).它的工作原理是在您的 UITextView 后面添加一个UITextField - 在正确的位置 - 并利用它的占位符(因此您不必担心获得正确的颜色等),然后在您的 UITextView 更改为显示/隐藏时监听通知这个 UITextField (因此它不会干扰您现有的 UITextViewDelegate 调用)。 And there's no magic numbers involved... :-)并且没有涉及神奇的数字...... :-)

The objc_setAssociatedObject()/objc_getAssociatedObject() is to avoid having to subclass UITextView. objc_setAssociatedObject()/objc_getAssociatedObject() 是为了避免继承 UITextView。 [Unfortunately, to position the UITextField correctly, it was necessary to introduce a 'private' subclass, to override placeholderRectForBounds:] [不幸的是,为了正确定位 UITextField,有必要引入一个“私有”子类来覆盖 placeholderRectForBounds:]

Adapted from BobDickinson's Swift answer.改编自 BobDickinson 的 Swift 答案。

Simply create @IBDesignable subclass of your UITextView :只需创建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)
    }
}

and then simply setup your placeholder in identity inspector :然后只需在身份检查器中设置您的占位符:

在此处输入图像描述

This is my version of UITextView with placeholder support.这是我的 UITextView 版本,支持占位符。 Swift 4.2 https://gist.github.com/hlung/c5dda3a0c2087e5ae6c1fce8822c4713斯威夫特 4.2 https://gist.github.com/hlung/c5dda3a0c2087e5ae6c1fce8822c4713

A UITextView subclass with placeholder text support.具有占位符文本支持的 UITextView 子类。 It uses another UILabel to show the placeholder, shown when text is empty.它使用另一个 UILabel 来显示占位符,当文本为空时显示。

In swift 5. Works fine.在 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
    }
}

After going through all answers, I realised I need something simple, and at the same time reusable so that I can add the same functionality to all UITextViews in my project.在浏览完所有答案后,我意识到我需要一些简单的东西,同时可以重用,这样我就可以向我项目中的所有UITextViews添加相同的功能。 And I ended up with the following code:最后我得到了以下代码:

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
}

} }

When UILabel is added as subView to UITextView , textView cursor position and UILabel position will mismatch, for that reason leave one space before your placeholder textUILabel作为 subView 添加到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
}

Then in textViewdidChange method add the following code然后在textViewdidChange方法中添加以下代码

func textViewDidChange(_ textView: UITextView) {
    if textView.text.isEmpty {
        textView.showPlaceHolderLabel()
    } else {
        textView.hidePlaceHolderLabel()
    }
}

It can be reused across all UITextViews .它可以在所有UITextViews中重复使用。

Simple class to support icon attribted placeholders in UITextView PlaceholderTextView在 UITextView PlaceholderTextView中支持图标属性占位符的简单类

@IBOutlet weak var tvMessage: PlaceholderTextView!
//  TODO: - Create Icon Text Attachment
let icon: NSTextAttachment = NSTextAttachment()
icon.image = UIImage(named: "paper-plane")
let iconString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: icon))

tvMessage.icon = icon

//  TODO: - Attributes
let textColor = UIColor.gray
let lightFont = UIFont(name: "Helvetica-Light", size: tvMessage.font!.pointSize)
let italicFont = UIFont(name: "Helvetica-LightOblique", size: tvMessage.font!.pointSize)

//  TODO: - Placeholder Attributed String
let message = NSAttributedString(string: " " + "Personal Message", attributes: [ NSFontAttributeName: lightFont!,   NSForegroundColorAttributeName: textColor])
iconString.append(message)
// TODO: - Italic Placeholder Part
let option = NSAttributedString(string: " " + "Optional", attributes: [ NSFontAttributeName: italicFont!, NSForegroundColorAttributeName: textColor])
iconString.append(option)

tvMessage.attributedPlaceHolder = iconString

tvMessage.layoutSubviews()

空的 带文字

The easiest method to modify the placeholder text color is through the XCode storyboard interface builder.修改占位符文本颜色的最简单方法是通过 XCode 故事板界面构建器。 Select the UITextField of interest and open the identity inspector on the right.选择感兴趣的 UITextField 并打开右侧的身份检查器。 Click on the plus symbol in the User Defined Runtime Attributes and add a new row with Key Path as _placeholderLabel.textColor, Type as Color and Value to your desired color.单击 User Defined Runtime Attributes 中的加号并添加一个新行,其中 Key Path 为 _placeholderLabel.textColor,Type 为 Color,Value 为您想要的颜色。

I was able to do add a "place holder" to a UITextView with ALOT less code.我能够用更少的代码向 UITextView 添加一个“占位符”。 This is what I did:这就是我所做的:

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;

I guess it's not an actual placeholder because you have to delete the text before you write but it could help if you wanted something a bit more simple.我想这不是一个实际的占位符,因为您必须在编写之前删除文本,但如果您想要更简单的东西,它可能会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM