Espace réservé dans UITextView

717

Mon application utilise un UITextView. Maintenant, je veux UITextViewavoir un espace réservé similaire à celui que vous pouvez définir pour un UITextField.

Comment faire ça?

142019
la source
Le TTTextEditor de Three20 (lui-même utilisant UITextField) prend en charge le texte d'espace réservé ainsi que la croissance en hauteur (il se transforme en UITextView).
Josh Benjamin
20
Que diriez-vous d'utiliser la catégorie UITextView + Placeholder? github.com/devxoul/UITextView-Placeholder
devxoul
2
Je préfère la solution de @ devxoul car elle utilise la catégorie, pas la sous-classe. Et il crée également un champ pour les options de «marque de réservation» (texte de marque de réservation et couleur du texte) dans l'inspecteur d'IB. Il utilise certaines techniques de reliure. Quel bon code
samthui7
Si vous utilisez UITextViewla solution devient tout à fait différente. Voici quelques solutions.
Espace réservé

Réponses:

672

J'ai apporté quelques modifications mineures à la solution de bcd pour permettre l'initialisation à partir d'un Xibfichier, l'habillage du texte et conserver la couleur d'arrière-plan. Espérons que cela sauvera les autres.

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
Jason George
la source
2
dans certains cas (en particulier la compatibilité iOS 5), il est nécessaire de remplacer le collage: - (void) paste: (id) sender {[super paste: sender]; [self textChanged: nil]; }
Martin Ullrich
3
Bon produit! Rappel sur les meilleures pratiques pour NSString (ou toute classe qui a un équivalent NSMutableXXX), la propriété doit être "copier" et non "conserver".
Oli
2
Comment instanciez-vous ce code? Je ne vois aucun texte d'espace réservé et rien ne s'efface lorsque je commence à taper.
user798719
40
Il s'agit d'une implémentation très, très mal écrite. Voici une version beaucoup plus propre qui surveille également les changements de dictée: github.com/cbowns/MPTextView
cbowns
10
Ne modifiez pas la hiérarchie des vues dans drawRect.
Karmeye
634

Un moyen simple, il suffit de créer du texte d'espace réservé en UITextViewutilisant les UITextViewDelegateméthodes suivantes :

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

n'oubliez pas de définir myUITextViewavec le texte exact sur la création par exemple

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

et rendre la classe parente a UITextViewDelegateavant d'inclure ces méthodes, par exemple

@interface MyClass () <UITextViewDelegate>
@end

Code pour Swift 3.1

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

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

n'oubliez pas de définir myUITextViewavec le texte exact sur la création par exemple

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

et rendre la classe parente a UITextViewDelegateavant d'inclure ces méthodes, par exemple

class MyClass: UITextViewDelegate
{

}
CmKndy
la source
1
C'est génial (j'aime simple) pour 1 écran avec 1 UITextView. La raison des solutions plus compliquées est que si vous avez une application plus grande avec de nombreux écrans et de nombreux UITextViews, vous ne voulez pas le faire encore et encore. Vous voudrez probablement sous-classer UITextView pour répondre à vos besoins, puis l'utiliser.
ghostatron
42
Si quelqu'un tape "texte d'espace réservé ici ..." dans la zone de texte, il se comporte également comme du texte d'espace réservé. De plus, lors de la soumission, vous devrez vérifier tous ces critères.
Anindya Sengupta
7
Le texte d'espace réservé est censé s'afficher même lorsque le champ devient le répondeur, et cette méthode ne fonctionnera pas pour cela.
phatmann
17
@jklp Je dirais que la méthode "sur-conçue" est plus propre et plus réutilisable ... et il semble qu'elle ne falsifie pas l' textattribut de la vue de texte qui est plutôt sympa ... alors que cette méthode le modifie
Cameron Askew
2
pourquoi les appels à devenirFirstResponder et resignFirstResponder dans les méthodes déléguées?
Adam Johns
119

Je n'étais pas trop satisfait de l'une des solutions publiées car elles étaient un peu lourdes. Ajouter des vues à la vue n'est pas vraiment idéal (surtout dans drawRect:). Ils ont tous deux eu des fuites, ce qui n'est pas acceptable non plus.

Voici ma solution: 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

C'est beaucoup plus simple que les autres, car il n'utilise pas de sous-vues (ou n'a pas de fuites). Sentez-vous libre de l'utiliser.

Mise à jour 11/10/11: elle est désormais documentée et prend en charge l'utilisation dans Interface Builder.

Mise à jour 24/11/13: pointez sur le nouveau référentiel.

Sam Soffes
la source
J'aime votre solution, et j'ai ajouté un remplacement de setTextpour également mettre à jour l'espace réservé lors du changement de propriété de texte par programme: - (void) setText: (NSString *) string {[super setText: string]; [self _updateShouldDrawPlaceholder]; }
olegueret
1
J'aime aussi votre solution mais vous avez manqué la méthode awakefromnib donc votre méthode init ne sera pas toujours appelée. Je l'ai pris de l'un des autres ici.
toxaq
Remarque - les nombres magiques varient en fonction de la taille de la police. Le positionnement exact peut être calculé à partir de la police, mais ne vaut probablement pas la peine pour cette implémentation. Le positionnement correct de l'espace réservé doit être de 2 pixels à droite de la position du curseur lorsqu'il n'y a pas de texte.
memmons
Pour résoudre le chargement depuis nib: vous voudrez probablement ajouter un - (id) initWithCoder: (NSCoder *) aDecoder; initialiseur pour accompagner l'existant.
Joris Kluivers
C'est mignon. L' notificationargument est mal orthographié dans la dernière méthode, cependant, et c'est un changement trop petit pour être soumis en tant que modification.
Phil Calvin
53

Je me suis trouvé un moyen très simple d'imiter un espace réservé

  1. dans la NIB ou le code, définissez textColor de votre textView sur lightGrayColor (la plupart du temps)
  2. assurez-vous que le délégué de votre textView est lié au propriétaire du fichier et implémentez UITextViewDelegate dans votre fichier d'en-tête
  3. définissez le texte par défaut de votre affichage de texte sur (exemple: "Espace réservé Foobar")
  4. implémenter: (BOOL) textViewShouldBeginEditing: (UITextView *) textView

Éditer:

Modification des instructions if pour comparer les balises plutôt que le texte. Si l'utilisateur supprimait son texte, il était également possible de supprimer accidentellement une partie de l'espace @"Foobar placeholder"réservé. Cela signifiait que si l'utilisateur saisissait à nouveau le texteVoir la méthode déléguée suivante -(BOOL) textViewShouldBeginEditing:(UITextView *) textView, cela ne fonctionnerait pas comme prévu. J'ai essayé de comparer par la couleur du texte dans l'instruction if, mais j'ai constaté que la couleur gris clair définie dans le générateur d'interface n'est pas la même que la couleur gris clair définie dans le code avec[UIColor lightGreyColor]

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

Il est également possible de réinitialiser le texte de l'espace réservé lorsque le clavier revient et que [textView length] == 0

ÉDITER:

Juste pour rendre la dernière partie plus claire - voici comment vous pouvez redéfinir le texte de l'espace réservé:

- (void)textViewDidChange:(UITextView *)textView
{
   if([textView.text length] == 0)
   {
       textView.text = @"Foobar placeholder";
       textView.textColor = [UIColor lightGrayColor];
       textView.tag = 0;
   }
}
vdevos
la source
12
J'aime beaucoup cette approche! La seule chose que je ferais pour la modification ci-dessus serait de déplacer l'implémentation hors de la méthode textViewDidChange: et dans la méthode textViewDidEndEditing:, de sorte que le texte d'espace réservé ne retourne qu'une fois que vous avez fini de travailler avec l'objet.
horseshoe7
52

Ce que vous pouvez faire est de configurer la vue texte avec une valeur initiale dans la textpropriété et de changer la textColoren [UIColor grayColor]ou quelque chose de similaire. Ensuite, chaque fois que la vue de texte devient modifiable, effacez le texte et présentez un curseur, et si le champ de texte est à nouveau vide, remettez votre texte d'espace réservé. Changez la couleur [UIColor blackColor]comme approprié.

Ce n'est pas exactement la même chose que la fonctionnalité d'espace réservé dans un UITextField, mais elle est proche.

Tim
la source
9
J'ai toujours utilisé lightGrayColor , qui semble correspondre à la couleur du texte de l'espace réservé.
Bill
Je lis juste ceci maintenant, mais je veux ajouter que la réinitialisation de la couleur au noir et la réinitialisation de la propriété text dans textViewShouldBeginEditing: (UITextView *) textView fonctionne très bien. C'est une solution très agréable et rapide par rapport aux solutions ci-dessous (mais toujours des solutions élégantes ci-dessous, sous-classant uitextview. Beaucoup plus modulaire).
Enrico Susatyo
3
Certes, mais il n'imite pas le comportement de UITextField, qui ne remplace son texte d'espace réservé que lorsque l'utilisateur tape quelque chose, pas au début de la modification, et qui ajoute à nouveau l'espace réservé à la seconde où la vue est vide, pas lorsque l'édition se termine réellement.
Ash
47

Vous pouvez définir l'étiquette sur le UITextViewpar

[UITextView addSubView:lblPlaceHoldaer];

et le cacher sur la TextViewdidChangeméthode.

C'est la manière simple et facile.

MohammedYakub Moriswala
la source
45

Si quelqu'un a besoin d'une solution pour Swift:

Ajoutez UITextViewDelegate à votre classe

var placeHolderText = "Placeholder Text..."

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

func textViewShouldBeginEditing(textView: UITextView) -> Bool {

    self.textView.textColor = .black

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

    return true
}

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

override func viewWillAppear(animated: Bool) {

    if(currentQuestion.answerDisplayValue == "") {
        self.textView.text = placeHolderText
        self.textView.textColor = .lightGray
    } else {
        self.textView.text = "xxx" // load default text / or stored 
        self.textView.textColor = .black
    }
}
derdida
la source
c'est ok mais pas assez bon. si l'utilisateur tape "Placeholder Text ..." (cas de bord évidemment), cela casse votre logique
Lucas Chwe
45

Solution simple Swift 3

Ajoutez UITextViewDelegateà votre classe

Ensemble yourTextView.delegate = self

Créez placeholderLabelet positionnez-le à l'intérieuryourTextView

Maintenant animer tout placeholderLabel.alphasur textViewDidChange:

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

vous devrez peut-être jouer avec la placeholderLabelposition pour bien la régler, mais cela ne devrait pas être trop difficile

budidino
la source
1
Grande réponse, solution simple. J'ai ajouté une petite amélioration pour n'animer que lorsque l'alpha doit changer: let alpha = CGFloat(textView.text.isEmpty ? 1.0 : 0.0) if alpha != lblPlaceholder.alpha { UIView.animate(withDuration: 0.3) { self.lblPlaceholder.alpha = alpha } }
Luciano Sclovsky
24

J'ai étendu la réponse de KmKndy, de sorte que l'espace réservé reste visible jusqu'à ce que l'utilisateur commence à modifier le UITextViewplutôt que de simplement appuyer dessus. Cela reflète la fonctionnalité des applications Twitter et Facebook. Ma solution ne vous oblige pas à sous-classer et fonctionne si l'utilisateur tape directement ou colle du texte!

Exemple d'espace réservé Twitter App

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

}

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

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

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

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

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

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

    return YES;
}

n'oubliez pas de définir myUITextView avec le texte exact lors de la création, par exemple

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

et faire de la classe parent un délégué UITextView avant d'inclure ces méthodes, par exemple

@interface MyClass () <UITextViewDelegate>
@end
Matt
la source
20

Je recommande d'utiliser SZTextView.

https://github.com/glaszig/SZTextView

Ajoutez votre valeur UITextViewpar défaut à partir de storyboard, puis modifiez sa classe personnalisée SZTextViewcomme ci-dessous 👇👇👇👇

entrez la description de l'image ici

Ensuite, vous verrez deux nouvelles options dans le Attribute Inspector👇👇👇👇

entrez la description de l'image ici

Vaibhav Saran
la source
20

Ci-dessous se trouve un port Swift du code ObjC "SAMTextView" publié comme l'une des premières réponses à la question. Je l'ai testé sur iOS 8. J'ai modifié quelques éléments, y compris le décalage des limites pour le placement du texte de l'espace réservé, car l'original était trop haut et trop à droite (suggestion utilisée dans l'un des commentaires de ce post).

Je sais qu'il existe de nombreuses solutions simples, mais j'aime l'approche du sous-classement UITextView car elle est réutilisable et je n'ai pas à encombrer les classes en l'utilisant avec les mécanismes.

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

Swift 4.2:

import UIKit

class PlaceholderTextView: UITextView {

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

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

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

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

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

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

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

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

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

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

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

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

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

            placeholderText.draw(in: placeholderRectForBounds(bounds: bounds), withAttributes: attributes)
        }
        super.draw(rect)
    }
}
clearlight
la source
Merci d'avoir fait la version rapide, pouvez-vous expliquer à quoi sert une méthode awakeFromNib qui appelle simplement super?
Pierre
Il définit l'espace réservé, mais ne se met pas à jour une fois que vous commencez à taper.
David
Peu importe, j'ai mis l'appel de notification dans awakeFromNib et cela fonctionne maintenant.
David
12

voici comment je l'ai fait:

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
bcd
la source
12

Voici une solution beaucoup plus simple qui se comporte exactement comme l'espace réservé d'UITextField mais qui ne nécessite pas de dessiner des vues personnalisées ou de démissionner du premier répondant.

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

}

(la deuxième vérification dans l'instruction else if concerne le cas où rien n'est entré et l'utilisateur appuie sur la touche de retour arrière)

Définissez simplement votre classe comme UITextViewDelegate. Dans viewDidLoad, vous devez initialiser comme

- (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;
}
aning
la source
2
Le fait est que si l'utilisateur appuie quelque part au milieu du "texte d'espace réservé", le curseur reste là.
Alex Sorokoletov
10

Salut, vous pouvez utiliser IQTextView disponible dans IQKeyboard Manager, il est simple à utiliser et à intégrer, il suffit de définir la classe de votre textview à IQTextView et vous pouvez utiliser sa propriété pour définir l'étiquette d'espace réservé avec la couleur que vous voulez. Vous pouvez télécharger la bibliothèque depuis IQKeyboardManager

ou vous pouvez l'installer à partir de cocoapods.

Amit Shelgaonkar
la source
IQKeyboardManager est très utile et sans code. La meilleure réponse pour moi!
NSDeveloper
1
En fait, je vote positivement et laisse un commentaire pour la raison. Je ne sais pas qu'IQTextView est disponible dans IQKeyboardManager auparavant.
NSDeveloper
J'ai eu un problème avec le délégué. J'ai retiré de la classe le remplacement du délégué et cela UITextViewDelegatea très bien fonctionné
TheoK
réponse sous
estimée
10

Désolé d'ajouter une autre réponse, mais je viens de retirer quelque chose comme ça et cela a créé le type d'espace réservé le plus proche de UITextField.

J'espère que cela aide quelqu'un.

-(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);
    }
}
Shai Mishali
la source
1
J'ai dû changer l'ordre des commandes dans la première instruction if if(textView.textColor == [UIColor lightGrayColor]){ textView.textColor = [UIColor blackColor]; textView.text = [textView.text substringToIndex: 1]; Sinon, le premier caractère entré dans la vue de texte a été placé à la fin du texte
Flexicoder
7

Un moyen simple d'utiliser cela dans une ligne de code:

Prenez une étiquette jusqu'à UITextView dans .nib en connectant cette étiquette à votre code, après.

- (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;
}
SachinVsSachin
la source
7

J'ai modifié l'implémentation de Sam Soffes pour fonctionner avec 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;
}

N'oubliez pas de définir des _placeholderAttribues = nilméthodes susceptibles de modifier la police et les autres signes susceptibles de les affecter. Vous pouvez également ignorer la création "paresseuse" du dictionnaire d'attributs si cela ne vous dérange pas.

ÉDITER:

N'oubliez pas d'appeler setNeedsDisplay dans une version redéfinie de setBounds si vous souhaitez que l'espace réservé apparaisse bien après les animations de mise en page automatique et autres.

Cloueur
la source
Je pense que vous devriez ajouter insets.left au paramètre offset x.
Karmeye
n'est-ce pas setFrame au lieu de setBounds?
JakubKnejzlik
Nan! setFrame n'est pas appelé lors des animations de mise en page, semble-t-il.
Nailer
6

Vous pouvez également créer une nouvelle classe TextViewWithPlaceholder en tant que sous-classe de UITextView.

(Ce code est un peu grossier - mais je pense qu'il est sur la bonne voie.)

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

}

Dans votre délégué, ajoutez ceci:

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

}
Amagrammer
la source
1
Pour obtenir le comportement exact, vous devez peindre votre propre espace réservé en remplaçant drawRect: (dessiner l'espace réservé uniquement si! [Self isFirstResponder] && [[texte texte] longueur] == 0), et en appelant setNeedsDisplay à l'intérieur de devenirFirstResponder et resignFirstResponder
rpetrich
6

J'ai créé ma propre version de la sous-classe de 'UITextView'. J'ai aimé l' idée de Sam Soffes d'utiliser les notifications, mais je n'ai pas aimé le drawRect: écraser. Semble exagéré pour moi. Je pense que j'ai fait une mise en œuvre très propre.

Vous pouvez consulter ma sous-classe ici . Un projet de démonstration est également inclus.

gcamp
la source
6

Ce fil a eu beaucoup de réponses, mais voici la version que je préfère.

Il étend la UITextViewclasse existante et est donc facilement réutilisable, et il n'intercepte pas les événements comme textViewDidChange(ce qui pourrait casser le code de l'utilisateur, s'ils interceptaient déjà ces événements ailleurs).

En utilisant mon code (illustré ci-dessous), vous pouvez facilement ajouter un espace réservé à l'un de vos UITextViewssemblables:

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

Lorsque vous définissez cette nouvelle valeur d'espace réservé, elle ajoute discrètement un UILabelau-dessus de votre UITextView, puis la masque / affiche si nécessaire:

entrez la description de l'image ici

D'accord, pour apporter ces modifications, ajoutez un fichier "UITextViewHelper.h" contenant ce code:

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

... et un fichier UITextViewHelper.m contenant ceci:

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

@implementation UITextView (UITextViewHelper)

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

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

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

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

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

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

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

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

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

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

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

    [self.placeholderLabel sizeToFit];

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

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

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

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

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

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

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

Oui, c'est beaucoup de code, mais une fois que vous l'avez ajouté à votre projet et inclus le fichier .h ...

#import "UITextViewHelper.h"

... vous pouvez facilement utiliser des espaces réservés dans UITextViews.

Il y a un problème cependant.

Si tu fais ça:

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

... l'espace réservé apparaîtra en haut du texte. Lorsque vous définissez la textvaleur, aucune des notifications régulières n'est appelée, donc je n'ai pas pu trouver comment appeler ma fonction pour décider d'afficher / masquer l'espace réservé.

La solution consiste à définir textValueplutôt que text:

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

Vous pouvez également définir la textvaleur, puis appeler checkIfNeedToDisplayPlaceholder.

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

J'aime les solutions comme celle-ci, car elles «comblent l'écart» entre ce que Apple nous fournit et ce dont nous (en tant que développeurs) avons réellement besoin dans nos applications. Vous écrivez ce code une fois, l'ajoutez à votre bibliothèque de fichiers "helper" .m / .h et, avec le temps, le SDK commence à devenir moins frustrant.

(J'ai écrit une aide similaire pour ajouter un bouton "effacer" à mes UITextViews, une autre chose qui existe énormément dans UITextFieldmais pas dans UITextView...)

Mike Gledhill
la source
J'aime à quel point c'est propre, mais je n'aime pas à quel point cela nécessite une deuxième UIView / UILabel (et n'hérite pas vraiment les attributs / couleurs / police facilement de l'UITextView). Belle contribution cependant
mattsven
6

Prenez d'abord une étiquette dans le fichier .h.

Ici je prends

UILabel * lbl;

Puis dans .m sous viewDidLoad, déclarez-le

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 est mon TextView.

Déclarez maintenant

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

 if (![textView hasText]){

    lbl.hidden = NO;

 }
 else{
    lbl.hidden = YES;
 }

}

Et votre espace réservé Textview est prêt!

Tania S
la source
6

Je recommande d'utiliser le pod 'UITextView + Placeholder'

pod 'UITextView+Placeholder'

sur votre code

#import "UITextView+Placeholder.h"

////    

UITextView *textView = [[UITextView alloc] init];
textView.placeholder = @"How are you?";
textView.placeholderColor = [UIColor lightGrayColor];
Felix Cruz
la source
5
    - (void)textViewDidChange:(UITextView *)textView
{
    placeholderLabel.hidden = YES;
}

mettre une étiquette sur la vue de texte.

vatti
la source
Ou encore mieux, vous pouvez l'afficher à nouveau en l'absence de texte: lblPlaceholder.hidden =! [TextView.text isEqualToString: @ ""];
Despotovic
J'aime cette solution propre, pas d'injection à textview lui-même.
Itachi
5

Il n'est pas possible de créer un espace réservé dans UITextView mais vous pouvez générer un effet comme espace réservé par cela.

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

OU vous pouvez ajouter une étiquette dans l'affichage de texte comme

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

Et mettre

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

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

Cela imite parfaitement l'espace réservé d'UITextField, où le texte de l'espace réservé reste jusqu'à ce que vous tapiez réellement quelque chose.

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
        }
    }
}
AmitP
la source
4

Voici encore une autre façon de le faire, qui reproduit la légère indentation de UITextFieldl'espace réservé de:

Faites glisser un UITextFielddroit sous le UITextViewafin que leurs coins supérieurs gauche soient alignés. Ajoutez votre texte d'espace réservé au champ de texte.

Dans viewDidLoad, ajoutez:

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

Puis ajouter:

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

Permet de le rendre facile

Créez un UILabel et placez-le sur votre vue de texte (Donnez le texte en tant que couleur définie par l'espace réservé-vous pouvez faire tout cela dans votre xib) lorsque vous cliquez sur la vue texte

code complet ci-dessous

entête

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

@end

la mise en oeuvre

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

@fin

N'oubliez pas de connecter textView et UILabel au propriétaire de fichiers de xib

Nithin M Keloth
la source
4

Jetez un œil à UTPlaceholderTextView .

Il s'agit d'une sous-classe pratique de UITextView qui prend en charge un espace réservé similaire à celui de UITextField. Particularités principales:

  • N'utilise pas de sous-vues
  • Ne remplace pas drawRect:
  • L'espace réservé peut être de longueur arbitraire et rendu exactement de la même manière que le texte habituel
Granfalloner
la source
4

J'ai lu tout cela, mais j'ai trouvé une solution très courte, Swift 3, qui a fonctionné dans tous mes tests. Cela pourrait être un peu plus général, mais le processus est simple. Voici tout ce que j'appelle "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()
    }
}
P. Ent
la source
Quelques préoccupations ici. Vous ne devez pas appeler layoutSubviews()directement. Et vous n'avez pas supprimé l'observateur NotificationCenter.
Hlung
4

J'ai écrit un cours en swift. Vous pouvez importer cette classe à tout moment.

import UIKit

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

}

entrez la description de l'image ici

Sujatha Girijala
la source
1
C'est en fait l'une des solutions les plus propres que j'ai pu trouver et j'ai fini par l'utiliser. Surtout en tenant compte des encarts et en utilisant des contraintes pour l'étiquette, c'est une bonne idée, je n'ai pas vu beaucoup (aucune?) D'autres solutions prendre soin de changer les limites, les polices, le RTL etc.
Mark