Заполнитель в UITextView

657

Я делаю приложение, которое использует UITextView. Теперь я хочу, чтобы UITextView имел местозаполнитель, похожий на тот, который вы можете установить для UITextField.

Кто-нибудь знает, как это сделать?

  • 0
    TTTextEditor от Three20 (сам использующий UITextField) поддерживает заполнитель текста, а также рост по высоте (он превращается в UITextView).
Показать ещё 5 комментариев
Теги:
cocoa-touch

56 ответов

677
Лучший ответ

Я сделал несколько незначительных изменений в решении bcd, чтобы разрешить инициализацию из файла Xib, перенос текста и поддерживать фоновый цвет. Надеюсь, это спасет других от неприятностей.

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:

#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
  • 0
    Каждый раз, когда drawRect вызывается, новая UILabel добавляется в иерархию представления. Я не слишком много работал с drawRect - есть ли предположение, что в иерархии представления нет постоянства, так что каждый раз, когда drawRect вызывается, ранее добавленное подпредставление метки было выпущено?
  • 0
    В целом, хорошая реализация, но я должен согласиться с ответом Сэма Соффса - добавление метки в иерархию представлений довольно тяжело для того, что нужно. Кроме того, его реализация _updateShouldDrawPlaceholder и использование setNeedsDisplay более тщательны.
Показать ещё 21 комментарий
568

Простой способ, просто создайте текст-заполнитель в UITextView с помощью следующих методов UITextViewDelegate:

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

просто не забудьте установить myUITextView точный текст при создании, например.

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

и сделайте родительский класс a UITextViewDelegate, прежде чем включать эти методы, например.

@interface MyClass () <UITextViewDelegate>
@end

Код для Swift 3.1

func textViewDidBeginEditing(_ textView: UITextView) 
{
    if (textView.text == "placeholder text here...")
    {
        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()
}

просто не забудьте установить myUITextView точный текст при создании, например.

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

и сделайте родительский класс a UITextViewDelegate, прежде чем включать эти методы, например.

class MyClass: UITextViewDelegate
{

}
  • 1
    Это здорово (я люблю простой) для 1 экрана с 1 UITextView. Причина более сложных решений заключается в том, что если у вас есть более крупное приложение с МНОГИМИ экранами и МНОГИМИ UITextViews, вы не хотите делать это снова и снова. Вы, вероятно, хотите создать подкласс UITextView, чтобы соответствовать вашим потребностям, а затем использовать это.
  • 38
    Если кто-то печатает «текст-заполнитель здесь ...» в текстовом поле, он также ведет себя как текст-заполнитель. Кроме того, во время подачи вам нужно будет проверить все эти критерии.
Показать ещё 10 комментариев
111

Я не был доволен решением, которое было опубликовано, поскольку они были немного тяжелыми. Добавление представлений в представление не является идеальным (особенно в drawRect:). У них обоих были утечки, что также неприемлемо.

Вот мое решение: SAMTextView

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

Это намного проще, чем другие, поскольку он не использует subviews (или имеет утечки). Не стесняйтесь использовать его.

Обновление 11/10/11: Теперь оно задокументировано и поддерживает использование в Interface Builder.

Обновление 11/24/13: Укажите новое репо.

  • 0
    Мне нравится ваше решение, и я добавил переопределение setText чтобы также обновлять заполнитель при программном изменении свойства текста: - (void) setText: (NSString *) string {[super setText: string]; [self _updateShouldDrawPlaceholder]; }
  • 0
    Спасибо! Я добавил это в свой класс: soff.me/2Jds
Показать ещё 9 комментариев
51

Что вы можете сделать, это настроить текстовое представление с некоторым начальным значением в свойстве text и изменить textColor на [UIColor grayColor] или что-то подобное. Затем, всякий раз, когда текстовое представление становится редактируемым, очищайте текст и представляйте курсор, а если текстовое поле снова пусто, верните текст заполнителя. При необходимости измените цвет на [UIColor blackColor].

Это не то же самое, что и функция-заполнитель в UITextField, но она закрывается.

  • 9
    Я всегда использовал lightGrayColor , который, кажется, соответствует цвету текста заполнителя.
  • 0
    Я просто сейчас читаю это, но хочу добавить, что сброс цвета на черный и сброс свойства text в textViewShouldBeginEditing: (UITextView *) textView работает очень хорошо. Это очень приятное и быстрое решение по сравнению с решениями ниже (но все же элегантные решения ниже, подклассы uitextview. Гораздо более модульные).
Показать ещё 1 комментарий
49

Я нашел себе простой способ подражать владельцу места

  • в NIB или код установите для textView textColor значение lightGrayColor (большую часть времени)
  • убедитесь, что ваш делегат textView связан с владельцем файла и реализует UITextViewDelegate в вашем файле заголовка.
  • установите текст текста по умолчанию (например: "Заполнитель Foobar" )
  • реализовать: (BOOL) textViewShouldBeginEditing: (UITextView *) textView

Edit:

Изменены операторы if, чтобы сравнивать теги, а не текст. Если пользователь удалил свой текст, можно было также случайно удалить часть держателя места @"Foobar placeholder". Это означало, что если пользователь повторно вошел в textView, следующий метод делегата -(BOOL) textViewShouldBeginEditing:(UITextView *) textView, он не будет работать так, как ожидалось. Я попытался сравнить цвет текста в выражении if, но обнаружил, что светло-серый цвет, заданный в построителе интерфейса, не совпадает с светло-серым цветом, установленным в коде с [UIColor lightGreyColor]

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

Также возможно reset текст заполнителя при возврате клавиатуры и [длина текста] == 0

EDIT:

Просто, чтобы сделать последнюю часть более четкой - вот как вы можете вернуть текст заполнителя:

- (void)textViewDidChange:(UITextView *)textView
{
   if([textView.text length] == 0)
   {
       textView.text = @"Foobar placeholder";
       textView.textColor = [UIColor lightGrayColor];
       textView.tag = 0;
   }
}
  • 11
    Мне очень нравится этот подход! Единственное, что я хотел бы сделать для редактирования выше, это переместить реализацию из textViewDidChange: method в textViewDidEndEditing: метод, чтобы заполнитель текста возвращался только после того, как вы закончили работать с объектом.
39

Вы можете установить метку на UITextView на

[UITextView addSubView:lblPlaceHoldaer];

и спрячьте его в методе TextViewdidChange.

Это простой и легкий способ.

34

Если кому-то нужно решение для Swift:

Добавить UITextViewDelegate в класс

var placeHolderText = "Placeholder Text..."

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

func textViewShouldBeginEditing(textView: UITextView) -> Bool {

    self.textView.textColor = UIColor.blackColor()

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

    return true
}

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

override func viewWillAppear(animated: Bool) {

    if(currentQuestion.answerDisplayValue == "") {
        self.textView.text = placeHolderText
        self.textView.textColor = UIColor.lightGrayColor()
    } else {
        self.textView.text = "xxx" // load default text / or stored 
        self.textView.textColor = UIColor.blackColor()
    }
}
  • 0
    это нормально, но недостаточно хорошо. если пользователь вводит «Placeholder Text ...» (очевидно, это крайний случай), это нарушает вашу логику
23

Я продлил ответ KmKndy, так что местозаполнитель остается видимым до тех пор, пока пользователь не начнет редактировать UITextView, а не просто нажимает на него. Это отражает функциональность в приложениях Twitter и Facebook. Мое решение не требует от вас подкласса и работает, если пользователь вводит непосредственно или вставляет текст!

Изображение 5264Изображение 5265

- (void)textViewDidChangeSelection:(UITextView *)textView{
    if ([textView.text isEqualToString:@"What 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 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 happening?";
        textView.textColor = [UIColor lightGrayColor];
        [textView setSelectedRange:NSMakeRange(0, 0)];
    }
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    if ([textView.text isEqualToString:@""]) {
        textView.text = @"What 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 happening?"]) {
         textView.text = @"";
         textView.textColor = [UIColor blackColor];
    }

    return YES;
}

просто не забудьте установить myUITextView с точным текстом при создании, например.

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

и сделайте родительский класс делегатом UITextView, прежде чем включать эти методы, например.

@interface MyClass () <UITextViewDelegate>
@end
19

Простое решение Swift 3

Добавьте UITextViewDelegate в свой класс

Установить yourTextView.delegate = self

Создайте placeholderLabel и расположите его внутри yourTextView

Теперь просто оставьте placeholderLabel.alpha на textViewDidChange:

  // MARK: TextView Delegate
  func textViewDidChange(_ textView: UITextView) {
    UIView.animate(withDuration: 0.3) {
      self.placeholderLabel.alpha = textView.text.isEmpty ? 1 : 0
    }
  }

вам может потребоваться сыграть с позицией placeholderLabel, чтобы настроить его правильно, но это не должно быть слишком сложно.

  • 1
    Отличный ответ, простое решение. Я добавил небольшое улучшение для анимации, только когда альфа должна измениться: let alpha = CGFloat(textView.text.isEmpty ? 1.0 : 0.0) if alpha != lblPlaceholder.alpha { UIView.animate(withDuration: 0.3) { self.lblPlaceholder.alpha = alpha } }
  • 1
    @LucianoSclovsky ваше небольшое улучшение действительно работает для меня
18

Я рекомендую использовать SZTextView.

https://github.com/glaszig/SZTextView

Добавьте по умолчанию UITextView из storyboard, а затем измените свой собственный класс на SZTextView, как показано ниже <

Изображение 5266

Затем вы увидите две новые опции в Attribute Inspector

Изображение 5267

12

Здесь проще найти решение, которое ведет себя точно так же, как и 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;
    }

}

(вторая проверка в инструкции else if для случая, когда ничего не вводится и пользователь нажимает обратно)

Просто установите свой класс как UITextViewDelegate. В 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;
}
  • 2
    Дело в том, что если пользователь нажимает где-то посередине «текста заполнителя», то каретка остается там.
12

вот как я это сделал:

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

@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
10

Извините, что добавил еще один ответ, но я просто вытащил что-то вроде этого, и это создало экземпляр с ближайшим к UITextField.

Надеюсь, это поможет кому-то.

-(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);
    }
}
  • 1
    Мне пришлось изменить порядок команд в первом операторе if(textView.textColor == [UIColor lightGrayColor]){ textView.textColor = [UIColor blackColor]; textView.text = [textView.text substringToIndex: 1]; В противном случае первый символ, вводимый в текстовое представление, помещается в конец текста.
  • 1
    Это работает для меня, просто измените substringToIndex 0
9

Ниже представлен Swift-порт кода Obam, который является одним из первых ответов на вопрос. Я протестировал его на iOS 8. Я изменил пару вещей, включая смещения, связанные с размещением текста-заполнителя, поскольку оригинал был слишком высоким и слишком правильным (использовалось предложение в одном из комментариев к этому сообщению).

Я знаю, что существует множество простых решений, но мне нравится подход к подклассу UITextView, поскольку он многократно используется, и мне не нужно загромождать классы, используя его с помощью механизмов.

Swift 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)
    }
}
  • 0
    Спасибо за выполнение быстрой версии, можете ли вы объяснить, какой смысл иметь метод awakeFromNib, который просто вызывает super?
  • 0
    Он устанавливает заполнитель, но не обновляется, когда вы начинаете печатать.
Показать ещё 2 комментария
7

Я изменил реализацию 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;
}

Не забудьте установить _placeholderAttribues = nil в методах, которые могут изменить шрифт и другие тики, которые могут повлиять на них. Вы также можете пропустить "ленивое" создание словаря атрибутов, если это вас не испугает.

EDIT:

Не забудьте вызвать setNeedsDisplay в переопределенной версии setBounds, если вам нравится, чтобы заполнитель выглядел хорошо после анимации автозапуска и т.п.

  • 0
    Я думаю, что вы должны добавить insets.left к параметру смещения x.
  • 0
    разве это не setFrame вместо setBounds?
Показать ещё 1 комментарий
7

Простой способ использования этого в некоторой строке кода:

Возьмите одну метку до UITextView в .nib подключив эту метку к вашему коду, После этого.

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

Сначала возьмите метку в файле .h.

Здесь я беру

UILabel * lbl;

Затем в .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 - это мой TextView.

Теперь объявите

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

 if (![textView hasText]){

    lbl.hidden = NO;

 }
 else{
    lbl.hidden = YES;
 }

}

И ваш заполнитель Textview готов!

  • 0
    Будьте проще, не можете просить большего! фантазии не нужны.
6

Привет, вы можете использовать IQTextView, доступный в IQKeyboard Manager, просто использовать и интегрировать только класс класса вашего текстового представления в IQTextView, и вы можете использовать его свойство для установки метки метки с желаемым цветом. Вы можете загрузить библиотеку из IQKeyboardManager

или вы можете установить его из cocoapods.

  • 0
    IQKeyboardManager очень полезен и не содержит кодов. Лучший ответ для меня!
  • 1
    На самом деле я приветствую и оставляю комментарий по причине. Я не знаю, IQTextView доступен в IQKeyboardManager раньше.
Показать ещё 1 комментарий
6

Я сделал свою собственную версию подкласса "UITextView". Мне понравилась идея Sam Soffes использования уведомлений, но мне не понравилось drawRect: overwrite. Кажется, переполняет меня. Я думаю, что я сделал очень чистую реализацию.

Вы можете посмотреть мой подкласс здесь. Также представлен демонстрационный проект.

6

Вы также можете создать новый класс TextViewWithPlaceholder в качестве подкласса UITextView.

(Этот код довольно груб, но я думаю, что он на правильном пути.)

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

}

В своем делегате добавьте следующее:

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

}
  • 1
    Чтобы получить точное поведение, вы должны нарисовать свой собственный заполнитель, переопределив drawRect: (нарисовать заполнитель, только если! [Self isFirstResponder] && [[self text] length] == 0), и вызывая setNeedsDisplay внутриcomeFirstResponder и resignFirstResponder
4

Невозможно создать местозаполнитель в 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];
    }
    }

ИЛИ вы можете добавить метку в текстовое окно, как

       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];

и установите

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

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

В этой ветке было много ответов, но вот версия, которую я предпочитаю.

Он расширяет существующий класс UITextView, поэтому его легко повторно использовать, и он не перехватывает события типа textViewDidChange (что может нарушить код пользователя, если они уже перехватывают эти события в другом месте).

Используя мой код (показано ниже), вы можете легко добавить местозаполнитель к любому из ваших UITextViews следующим образом:

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

Когда вы устанавливаете это новое значение placeholder, оно спокойно добавляет UILabel поверх вашего UITextView, затем скрывает/показывает его при необходимости:

Изображение 5268

Хорошо, чтобы внести эти изменения, добавьте файл 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 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, это много кода, но как только вы добавили его в свой проект и включили файл .h...

#import "UITextViewHelper.h"

... вы можете легко использовать заполнители в UITextViews.

Там есть что-то.

Если вы это сделаете:

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

... вместо текста появится местозаполнитель. Когда вы устанавливаете значение text, никто из обычных уведомлений не вызывается, поэтому я не мог понять, как вызвать мою функцию, чтобы решить, показывать/скрывать местозаполнитель.

Решение состоит в том, чтобы установить textValue, а не text:

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

В качестве альтернативы вы можете установить значение text, а затем вызвать checkIfNeedToDisplayPlaceholder.

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

Мне нравятся такие решения, поскольку они "заполняют пробел" между тем, что Apple предоставляет нам, и тем, что нам (как разработчикам) действительно нужно в наших приложениях. Вы пишете этот код один раз, добавьте его в свою библиотеку файлов "хелпер".m/.h, и со временем SDK начнет становиться менее расстраивающим.

(Я написал подобный помощник для добавления "чистой" кнопки в UITextViews, еще одна вещь, которая досадно существует в UITextField, но не в UITextView...)

  • 0
    Мне нравится, насколько это чисто, но мне не нравится, как для этого требуется второй UIView / UILabel (и на самом деле не легко наследовать атрибуты / цвета / шрифт из UITextView). Хороший вклад, хотя
4

Посмотрите UTPlaceholderTextView.

Это удобный подкласс UITextView, который поддерживает местозаполнитель, аналогичный элементу UITextField. Основные особенности:

  • Не использует subviews
  • Не отменяет drawRect:
  • Заполнитель может иметь произвольную длину и выводиться точно так же, как обычный текст
4

Давайте упростим

Создайте один UILabel и поместите его в текстовое представление (укажите текст как серый цвет серого цвета - вы можете сделать все это в своем xib) Теперь в вашем файле заголовка объявляем UILabel, а также textviewDelegate Теперь вы можете просто скрыть ярлык, когда вы нажимаете на textview

полный код ниже

заголовок

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

@end

реализация

@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

Не забудьте подключить textView и UILabel к файловому администратору из xib

4

Здесь есть еще один способ сделать это, который воспроизводит небольшой отступ UITextField placeholder:

Перетащите UITextField прямо под UITextView, чтобы их верхние левые углы были выровнены. Добавьте текст замещающего текста в текстовое поле.

В viewDidLoad добавьте:

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

Затем добавьте:

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

поместите надпись над текстом.

  • 0
    Или, что еще лучше, вы можете показать его снова, когда текст отсутствует: lblPlaceholder.hidden =! [TextView.text isEqualToString: @ ""];
3

Это хорошо подбирает местозаполнитель 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
        }
    }
}
3

OK мой ansewer немного отличается Я создаю небольшой класс, чтобы сделать это для вас.

Файл 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];

получайте удовольствие:)

3

Как вставить placeholder в UITextView?

Ответ PJR работает как шарм. Те, что здесь, не работали для меня. Возможно, iOS5.

3

Я создал переменную экземпляра, чтобы проверить, покажу ли местозаполнитель или нет:

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

В viewDidLoad я установил:

[self setPlaceHolder]; 

Вот что это делает:

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

Я также создал кнопку, чтобы отменить клавиатуру. Вам не обязательно это делать, но здесь все круто, что заполнитель снова отображается, если ничего не было введено

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

Я прочитал все это, но придумал очень короткое решение 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()
    }
}
  • 0
    Некоторые проблемы здесь. Вы не должны вызывать layoutSubviews() напрямую. И вы не удалили наблюдателя NotificationCenter.
2

Я знаю, что уже есть много ответов на этот вопрос, но я действительно не нашел их достаточным (по крайней мере, в Свифте). Мне понадобилась функциональность "placeholder" UITextField в моем UITextView (я хотел, чтобы точное поведение, включая атрибуты отображения текста, анимации и т.д., И не хотело поддерживать это со временем). Я также хотел получить решение, которое обеспечило бы ту же самую точную границу, что и UITextField (а не приближенное, похожее на то, как выглядит сейчас, но похожее на него и всегда будет выглядеть точно так же). Так что, хотя я и не был изначально фанатом для добавления дополнительного контроля в микс, казалось, что для достижения моих целей мне пришлось использовать фактический UITextField и позволить ему выполнять работу.

Это решение обрабатывает позиционирование заполнителя и синхронизацию шрифта между двумя элементами управления, так что текст-заполнитель является точным шрифтом и позицией в виде текста, введенного в элемент управления (что-то большее, чем другие решения не адресуют).

// 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;
    }
}
  • 0
    Ваш код падает. Рассмотрите возможность удаления fatalError
  • 0
    Я исправил это (дайте мне знать, если это работает). Раньше было трудно заставить инициализатор работать, поэтому я не поддерживал инициализацию Storyboard / XIB (мы не используем их в нашем приложении - мы создаем все элементы управления программно). Я возражаю против "вылетов". Цель fatalError заключалась в том, чтобы указать, что он не поддерживает инициализацию из Storyboard / XIB (хотя это, по общему признанию, было не очень ясно из сообщения).
2

Я написал более чистую реализацию, попробовав некоторые из предложенных подходов, и отправил ее в Github. Запросы и вопросы по запросу приветствуются.

Некоторые ключевые улучшения, которые существуют в сравнении с другими подходами, представленными здесь:

  • Не выделяет UILabel внутри drawRect:. (Пожалуйста, никогда не делайте этого.)
  • Не сравнивает текстовый вид текущего текста с требуемым заполнителем для замены цветов.
  • Скрывает местозаполнитель, в то время как диктовка активна (например, UITextField).
2
- (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 - это свойство, которое содержит рассматриваемый textView. Это сделает трюк.

1

Вот код для быстрого 3.1

Оригинальный код Джейсона Джорджа в первом ответе.

Не забудьте установить свой собственный класс для 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)
}
}
1

Swift 3.1

Попробовав все быстрые ответы, этот ответ спас бы мне 3 часа исследований. Надеюсь, это поможет.

  • Убедитесь, что ваш textField (независимо от вашего пользовательского имени) указывает на его делегат в Storyboard и имеет @IBOutlet с yourCustomTextField

  • Добавьте в viewDidLoad() следующее: оно появится при загрузке представления:

Покажите мне, как выглядит местозаполнитель:

yourCustomTextField = "Start typing..." 
yourCustomTextField.textColor = .lightGray
  1. Вне viewDidLoad, но внутри одного класса добавьте следующие объявления: UIViewController, UITextViewDelegate, UINavigationControllerDelegate

Этот код заставит 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()
}
1

Имитация родных заполнителей


Общей проблемой является то, что iOS не предоставляет встроенную функцию-заполнитель для текстовых просмотров. Расширение UITextView ниже пытается решить эту проблему, предлагая удобство, которое можно было бы ожидать от собственной функции, требуя, чтобы только одна строка кода добавляла местозаполнитель в экземпляр textview.

Недостатком этого решения является то, что он создает цепочку делегирования вызовов, он уязвим для (маловероятных) изменений к протоколу UITextViewDelegate в обновлении iOS. В частности, если iOS добавляет новые методы протокола и реализует любой из них в делегате для текстового представления с помощью заполнителя, эти методы не будут вызываться, если вы также не обновили расширение для переадресации этих вызовов.

В качестве альтернативы ответ Inline Placeholder является прочным и простеньким, как может быть.


Примеры использования:


    • Если в текстовом представлении, заполняющем заполнитель, не используется UITextViewDelegate:

    /* Swift 3 */

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

                        &nbsp                                           -

    • Если в текстовом представлении, набирающем местозаполнитель, используется символ 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 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 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 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. Как расширение основного класса iOS, такого как UITextView, важно знать, что этот код имеет без взаимодействия с любыми текстовыми изображениями, которые не активируют местозаполнитель, например экземпляры textview, которые не были инициализированы вызовом addPlaceholder()

2.. Текстовые представления с включенной подписью прозрачно становятся UITextViewDelegate, чтобы отслеживать количество символов, чтобы контролировать видимость заполнителя. Если делегат передан в addPlaceholder(), этот код-цепочка (то есть переадресация) делегирует обратные вызовы этому делегату.

3. Автор исследует способы проверки протокола UITextViewDelegate и прокси-сервера автоматически без необходимости жесткого кодирования каждого метода. Это приведет к модификации кода из изменений подписи метода и добавления новых методов в протокол.

1

Я написал класс в swift. Вы можете импортировать этот класс, когда требуется.

import UIKit

открытый класс 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)
}

}

Изображение 5269

  • 1
    Спасибо! Хорошее решение
  • 1
    На самом деле это одно из самых чистых решений, которое мне удалось найти, и в итоге я воспользовался этим. Особенно учитывая вставки и использование ограничений для метки - приятное прикосновение, я не видел, чтобы многие (какие?) Другие решения принимали забота об изменении границ, шрифтов, RTL и т. д.
1

Более простой подход заключается в создании вторичного UITextView со всеми теми же атрибутами, что и исходный текстовый вид, за исключением другого textColor, с ограничениями для обеспечения их выравнивания. Затем, когда любые символы вводятся в основное текстовое представление, скройте клонированный текстовый вид, иначе покажите клонированный текстовый вид с некоторым текстом.

Это может быть достигнуто несколькими способами, но относительно чистым способом будет подкласс UITextView и сохранить всю эту логику в подклассе.

Итак, подкласс UITextView и пусть он лениво создает это место:

Файл интерфейса:

@interface FOOTextView : UITextView <UITextViewDelegate>

@property (nonatomic, copy) NSString *placeholderText;

- (void)checkPlaceholder;

@end

Файл реализации:

#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

Используя вышеприведенный класс, если вы установите экземпляр делегата FOOTextView для себя, все будет работать из коробки:

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

Если вы хотите, чтобы другой объект захватил делегат, вам просто нужно вызвать метод viewPlaceholder текстового вида в методе textViewDidChange: delegate, например:

FOOTextView *myTextView = ...
myTextView.placeholderText = @"What 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];
}
1
- (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;
}
1

В классе .h

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

В классе .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];
    }
}
1

Вот простой и умный способ добиться совершенного поведения.

Возьмите заполнитель из UITextField.

Изображение 5270

  • Настройте текстовое поле и установите его текст прозрачным.

    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];
    
  • Установите делегат textView и синхронизируйте textField и textView.

    yourTextView.delegate = self;
    

    затем

    - (void)textViewDidChange:(UITextView *)textView {
    
        self.placeholderTextField.text = textView.text;
    
    }
    
  • Что все.
1

Мое решение GCPTextView - простая, легкая замена для UITextView, которая не полагается на UILabelView. Его можно найти на https://github.com/mbachrach/GCPTextView.

0

Самый простой способ изменить цвет текста заполнителя - через построитель интерфейса раскадровки XCode. Выберите интересующий UITextField и откройте инспектор идентификации справа. Нажмите на символ плюса в атрибутах Runtime User Defined Runtime и добавьте новую строку с Key Path как _placeholderLabel.textColor, введите цвет и значение в нужный цвет.

  • 0
    Не имеет ничего общего с вопросом или даже UITextView .
0

Просто создайте подкласс @IBDesignable вашего UITextView:

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

а затем просто установите ваш заполнитель в инспекторе идентификации:

Изображение 5271

0

После просмотра (и опробования) большинства предлагаемых решений этой, казалось бы, очевидной, но отсутствующей функции UITextView, "лучшим", который я нашел, было то, что от BobDickinson. Но мне не нравилось прибегать к совершенно новому подклассу [я предпочитаю категории "drop-in" для таких простых функциональных дополнений] и не перехватывать методы UITextViewDelegate, что, вероятно, испортит ваш существующий код обработки UITextView. Итак, здесь я беру категорию drop-in, которая будет работать с любым существующим экземпляром 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

Прост в использовании, просто очевидный

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

Он работает, добавляя UITextField - в нужном месте - за вашим UITextView и вместо этого вместо него используется вместо этого placeholder (поэтому вам не нужно беспокоиться о правильном цвете и т.д.), а затем прослушивать уведомления, когда ваш UITextView изменяется на показать/скрыть этот UITextField (и, следовательно, он не мешает вашим существующим вызовам UITextViewDelegate). И нет никаких магических чисел...: -)

Объект objc_setAssociatedObject()/objc_getAssociatedObject() должен избегать подкласса UITextView. [К сожалению, чтобы правильно позиционировать UITextField, необходимо было ввести подкласс 'private', чтобы переопределить placeholderRectForBounds:]

Адаптировано из ответа Боб Дикинсона Свифта.

0

Простой класс для поддержки значков, отмеченных заполнителями в 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()

Изображение 5272 Изображение 5273

0

Я только что нашел, что с iOS 10 теперь вы можете фактически применить метод UITextView к методу как UITextField и установить внутри метода placeholder. Просто попробовал, и он работает без подкласса UITextView.

Это пример того, что сработало для меня:

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

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

И чтобы использовать его для UITextView, вам просто нужно передать его методу, используя такой бросок:

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

N.B: После теста я выяснил, что решение отлично работает и на iOS9.x, но вызывает сбой на iOS8.x

0

Еще один ответ:

https://github.com/gcamp/GCPlaceholderTextView

Измените класс UITextView в IB на GCPlaceholderTextView и установите свойство placeholder

0

Я нашел собственное решение

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

Если вы ищете простой способ достичь этого, попробуйте мой подход:

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

Да, что он PLACE_HOLDER_TEXT является NSString, содержащим ваш placeholder

0

Ответ Джейсона будет немного похож на 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];
}
0

Более простой и по-прежнему учитывающий некоторый текст, введенный пользователем в какой-то момент

BOOL placeHolderTextVisible;

в viewDidLoad, установите его в YES (или DidMoveToSuperview или awakeFromNib)

то на - (BOOL) textView: (UITextView *) textView shouldBeginEditing

    - (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
{
   if (placeHolderTextVisible) {
    placeHolderTextVisible = NO;
    textView.text = @"";
   }
 return YES;
}
  • 0
    Это не решение вообще - после нажатия UITextView заполнитель исчезает и никогда не появляется снова.
  • 0
    Забудет ли пользователь, что может печатать, если удалит текст?
Показать ещё 1 комментарий
0

Three20 TTTextEditor (сам использует UITextField) поддерживает текст заполнителя, а также растет по высоте (он превращается в UITextView).

-2

Вы можете просто установить метку в текстовом виде.

MyUITextView.h

@interface MyUITextView : UITextView {
    UILabel* _placeholderLabel;
}

@property(nonatomic, assign)NSString *placeholder;

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

Мне удалось добавить "place place" в UITextView с меньшим количеством кода. Это то, что я сделал:

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

Я предполагаю, что это не фактический заполнитель, потому что вы должны удалить текст перед тем, как писать, но это может помочь, если вам нужно что-то более простое.

  • 0
    Я думаю, что этот ответ несправедливо недооценен. Это действительно аккуратное и умное решение. Кроме того, переопределение drawRect является довольно дорогой операцией. Несмотря на то, что требуется больше кода, чтобы скрыть и, возможно, переместить текстовое представление при изменении размера, я думаю, что это стоит повышения производительности.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню