我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
当前回答
这完美地模仿了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
}
}
}
其他回答
这个帖子已经有了很多答案,但这里是我更喜欢的版本。
它扩展了现有的UITextView类,因此很容易重用,并且它不拦截像textViewDidChange这样的事件(这可能会破坏用户的代码,如果他们已经在其他地方拦截了这些事件)。
使用我的代码(如下所示),你可以很容易地添加一个占位符到任何你的UITextViews像这样:
self.textViewComments.placeholder = @"(Enter some comments here.)";
当你设置这个新的占位符值时,它会悄悄地在你的UITextView上添加一个UILabel,然后在必要时隐藏/显示它:
好的,要做这些改变,添加一个包含以下代码的"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
…和一个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
是的,它有很多代码,但是一旦你把它添加到你的项目中,并包含了.h文件……
#import "UITextViewHelper.h"
...你可以很容易地在UITextViews中使用占位符。
不过有一个问题。
如果你这样做:
self.textViewComments.placeholder = @"(Enter some comments here.)";
self.textViewComments.text = @"Ooooh, hello there";
...占位符将出现在文本的顶部。设置文本值时,不会调用任何常规通知,因此我不知道如何调用我的函数来决定是否显示/隐藏占位符。
解决方案是设置textValue而不是text:
self.textViewComments.placeholder = @"(Enter some comments here.)";
self.textViewComments.textValue = @"Ooooh, hello there";
或者,您可以设置文本值,然后调用checkIfNeedToDisplayPlaceholder。
self.textViewComments.text = @"Ooooh, hello there";
[self.textViewComments checkIfNeedToDisplayPlaceholder];
我喜欢这样的解决方案,因为它们“填补了”苹果提供给我们的东西和我们(作为开发者)在应用程序中实际需要的东西之间的空白。只需编写一次此代码,将其添加到“helper”.m/.h文件库中,随着时间的推移,SDK实际上开始变得不那么令人沮丧。
(我写了一个类似的帮助添加一个“清除”按钮到我的UITextViews,另一个烦人的东西存在于UITextField但不在UITextView…)
更简单,并且仍然考虑到用户在某些时候输入的一些文本
BOOL placeHolderTextVisible;
在viewDidLoad上,设置它为YES(或DidMoveToSuperview,或awakeFromNib)
然后,在 - (BOOL) textView:(UITextView*)textView shouldBeginEditing
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
{
if (placeHolderTextVisible) {
placeHolderTextVisible = NO;
textView.text = @"";
}
return YES;
}
如果有人需要Swift的解决方案:
添加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
}
}
模拟本机占位符
一个常见的抱怨是iOS没有为文本视图提供原生占位符功能。下面的UITextView扩展试图通过提供人们期望从本机特性获得的便利来解决这个问题,只需要一行代码就可以向textview实例添加占位符。
这个解决方案的缺点是,因为它菊花链委托调用,它很容易(不太可能)在iOS更新中更改UITextViewDelegate协议。具体来说,如果iOS添加了新的协议方法,并且你在带有占位符的文本视图的委托中实现了它们中的任何一个,这些方法将不会被调用,除非你也更新了扩展来转发这些调用。
或者,内联占位符的答案是一个坚如磐石的和简单的可以。
使用例子:
•如果获得占位符的文本视图没有使用UITextViewDelegate:
/* Swift 3 */
class NoteViewController : UIViewController {
@IBOutlet weak var noteView: UITextView!
override func viewDidLoad() {
noteView.addPlaceholder("Enter some text...", color: UIColor.lightGray)
}
}
-- 或者——
•如果获得占位符的文本视图使用了UITextViewDelegate:
/* Swift 3 */
class NoteViewController : UIViewController, UITextViewDelegate {
@IBOutlet weak var noteView: UITextView!
override func viewDidLoad() {
noteView.addPlaceholder("Phone #", color: UIColor.lightGray, delegate: self)
}
}
实现(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. 作为一个像UITextView这样的基本iOS类的扩展,重要的是要知道这段代码没有与任何不激活占位符的textview交互,例如textview实例没有被调用addPlaceholder()初始化
2. 支持占位符的文本视图透明地变成一个UITextViewDelegate来跟踪字符计数,以控制占位符可见性。如果一个委托被传递给addPlaceholder(),这段代码会将委托回调给该委托。
3.作者正在研究如何检查UITextViewDelegate协议并自动代理它,而不必硬编码每个方法。这将使代码免受方法签名更改和添加到协议中的新方法的影响。
我做了我自己版本的UITextView的子类。我喜欢Sam Soffes使用通知的想法,但我不喜欢drawRect: overwrite。对我来说太过分了。我认为我做了一个非常干净的实现。
你可以看看我的子类。还包括一个演示项目。