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

如何做到这一点?


当前回答

好吧,我的阴沟有点不同 我创建了一个小班来为你做这件事。

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文件

#import <UIKit/UIKit.h>

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

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

这是简单的一行代码使用(不要忘记添加#import "TextViewShader.h")

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

玩得开心!

其他回答

另一个解决方案

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

结果:

- (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;
}

我通读了所有这些,但提出了一个非常简短的Swift 3解决方案,在我的所有测试中都有效。它可以更一般化一点,但过程很简单。这是整个东西,我称之为“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()
    }
}

在看完所有答案后,我意识到我需要一些简单的,同时可重用的东西,这样我就可以在我的项目中为所有uitextview添加相同的功能。最后我得到了以下代码:

extension UITextView {

// MARK: TextView PlaceHolderLabel Setup

func createPlaceHolderLabel(with text: String) {
    let lbl = UILabel()
    self.addSubview(lbl)

// Add your constraints here

    lbl.text = text
    lbl.textColor = .lightGray
}

// My Textview contains only one UILabel, and for my use case the below code works, tweak it according to your use case

// Lastly two methods to toggle between show and hide the placeholder label

func hidePlaceHolderLabel() {
    guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
    lbl.isHidden = true
}

func showPlaceHolderLabel() {
    guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
    lbl.isHidden = false
}

}

当UILabel作为子视图添加到UITextView时,textView游标位置和UILabel位置将不匹配,因此在占位符文本之前留下一个空间

class ViewController: UIViewController, UITextViewDelegate {
    var textView: UITextView!

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(textView)
    // Also setup textView constraints as per your need
    // Add placeholder to your textView
    // Leave one space before placeholder string
    textView.createPlaceHolderLabel(with: " Address")
    textView.delegate = self
}

然后在textViewdidChange方法中添加以下代码

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

它可以在所有uitextview中重用。

基于这里已经给出的一些很好的建议,我能够将以下轻量级的、与接口生成器兼容的UITextView子类组合在一起,它是:

包括可配置的占位符文本,样式就像UITextField一样。 不需要任何额外的子视图或约束。 不需要来自ViewController的任何委托或其他行为。 不需要任何通知。 保持该文本与查看字段的文本属性的任何外部类完全分离。

欢迎提出改进建议。

编辑1:更新为重置占位符格式,如果实际文本以编程方式设置。

编辑2:现在可以以编程方式检索占位符文本颜色。

斯威夫特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
    }
}