Comment puis-je ajouter du texte d'espace réservé à l'intérieur d'un UITextView dans Swift?


316

Je fais une application qui utilise un UITextView. Maintenant, je veux que la vue texte ait un espace réservé similaire à celui que vous pouvez définir pour un champ de texte. Comment pourriez-vous accomplir cela en utilisant Swift?


Il s'agit d'un problème séculaire dans le développement iOS avec UITextView. J'ai écrit des sous-classes comme celle mentionnée ici: stackoverflow.com/a/1704469/1403046 . L'avantage est que vous pouvez toujours avoir un délégué, ainsi qu'utiliser la classe à plusieurs endroits sans avoir à réimplémenter la logique.
cjwirth

Comment pourrais-je utiliser votre sous-classe, tout en utilisant swift pour le projet. Vous utilisez un fichier bridge?
StevenR

Vous pouvez le faire ou le réimplémenter dans Swift. Le code dans la réponse est plus long qu'il ne doit l'être. Le point principal étant d'afficher / masquer l'étiquette que vous ajoutez dans la méthode pour laquelle vous êtes averti lorsque le texte change.
cjwirth

Vous pouvez utiliser l'exemple UIFloatLabelTextView de GitHub. Cet espace réservé de position sur le dessus lors de l'écriture. Vraiment intéressant! github.com/ArtSabintsev/UIFloatLabelTextView
Jayprakash Dubey

2
Honnêtement, la façon la plus simple d'accomplir cela est d'avoir un textView personnalisé et d'ajouter simplement du texte d'espace réservé qui est dessiné sur le textView quand aucun texte n'est présent .... gestion des états (y compris les faux positifs pour quand le texte devrait / ne devrait pas exister / n'existe pas)
TheCodingArt

Réponses:


649

Mis à jour pour Swift 4

UITextViewn'a pas intrinsèquement de propriété d'espace réservé, vous devez donc en créer et en manipuler une par programmation à l'aide de UITextViewDelegateméthodes. Je recommande d'utiliser la solution # 1 ou # 2 ci-dessous en fonction du comportement souhaité.

Remarque: pour l'une ou l'autre solution, ajoutez UITextViewDelegateà la classe et définissez textView.delegate = selfpour utiliser les méthodes déléguées de la vue texte.


Solution n ° 1 - Si vous souhaitez que l'espace réservé disparaisse dès que l'utilisateur sélectionne la vue texte:

Définissez d'abord le UITextViewpour contenir le texte d'espace réservé et définissez-le sur une couleur gris clair pour imiter l'apparence du UITextFieldtexte d'espace réservé d'un. Faites-le dans la viewDidLoadou lors de la création de la vue texte.

textView.text = "Placeholder"
textView.textColor = UIColor.lightGray

Ensuite, lorsque l'utilisateur commence à modifier la vue texte, si la vue texte contient un espace réservé (c'est-à-dire si sa couleur de texte est gris clair), effacez le texte d'espace réservé et définissez la couleur du texte sur noir afin de s'adapter à l'entrée de l'utilisateur.

func textViewDidBeginEditing(_ textView: UITextView) {
    if textView.textColor == UIColor.lightGray {
        textView.text = nil
        textView.textColor = UIColor.black
    }
}

Ensuite, lorsque l'utilisateur a terminé de modifier la vue texte et qu'elle a démissionné en tant que premier répondant, si la vue texte est vide, réinitialisez son espace réservé en ajoutant à nouveau le texte d'espace réservé et en définissant sa couleur sur gris clair.

func textViewDidEndEditing(_ textView: UITextView) {
    if textView.text.isEmpty {
        textView.text = "Placeholder"
        textView.textColor = UIColor.lightGray
    }
}

Solution n ° 2 - Si vous souhaitez que l'espace réservé s'affiche chaque fois que la vue texte est vide, même si la vue texte est sélectionnée:

Définissez d'abord l'espace réservé dans viewDidLoad:

textView.text = "Placeholder"
textView.textColor = UIColor.lightGray

textView.becomeFirstResponder()

textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)

(Remarque: comme l'OP voulait que la vue texte soit sélectionnée dès que la vue se charge, j'ai incorporé la sélection de la vue texte dans le code ci-dessus. Si ce n'est pas le comportement souhaité et que vous ne voulez pas que la vue texte soit sélectionnée lors du chargement de la vue, supprimez les deux dernières lignes du bloc de code ci-dessus.)

Ensuite, utilisez la shouldChangeTextInRange UITextViewDelegateméthode, comme ceci:

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

    // Combine the textView text and the replacement text to
    // create the updated text string
    let currentText:String = textView.text
    let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)

    // If updated text view will be empty, add the placeholder
    // and set the cursor to the beginning of the text view
    if updatedText.isEmpty {

        textView.text = "Placeholder"
        textView.textColor = UIColor.lightGray

        textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
    }

    // Else if the text view's placeholder is showing and the
    // length of the replacement string is greater than 0, set 
    // the text color to black then set its text to the
    // replacement string
     else if textView.textColor == UIColor.lightGray && !text.isEmpty {
        textView.textColor = UIColor.black
        textView.text = text
    }

    // For every other case, the text should change with the usual
    // behavior...
    else {
        return true
    }

    // ...otherwise return false since the updates have already
    // been made
    return false
}

Et également implémenter textViewDidChangeSelectionpour empêcher l'utilisateur de changer la position du curseur alors que l'espace réservé est visible. (Remarque: textViewDidChangeSelectionest appelé avant le chargement de la vue, ne vérifiez la couleur de la vue texte que si la fenêtre est visible):

func textViewDidChangeSelection(_ textView: UITextView) {
    if self.view.window != nil {
        if textView.textColor == UIColor.lightGray {
            textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
        }
    }
}

5
Bonjour, assurez-vous de définir votre contrôleur de vue comme délégué pour votre textView . Vous pouvez y parvenir en créant une sortie de votre textView vers votre viewController. Ensuite, utilisez yourTextField.delegate = self. Si vous ne le faites pas, textViewDidBeginEditingles textViewDidEndEditingfonctions et ne fonctionneront pas.
Ujjwal-Nadhani

2
Le code ne se compile pas, j'ai une erreur comme Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>').
iPeter

4
J'ai trouvé la solution et je vais joindre le code révisé @iPeter. Le texte actuel doit être NSString formants: let currentText = textView.text as NSString?. Transformez la let updatedText =ligne en let updatedText = currentText?.replacingCharacters(in: range, with: text). Enfin, transformez la if updatedText.isEmptyligne en if (updatedText?.isEmpty)! {. Cela devrait faire l'affaire!
Baylor Mitchell

1
@ TàTruhoada J'ai mis à jour la solution pour gérer les scénarios de copier-coller
Lyndsey Scott

3
Le réglage @LyndseyScott textView.selectedTextRangede l'intérieur func textViewDidChangeSelection(_ textView: UITextView)provoque une boucle infinie ...
MikeG

229

Espace réservé flottant


Il est simple, sûr et fiable de positionner une étiquette d'espace réservé au-dessus d'une vue texte, de définir sa police, sa couleur et de gérer la visibilité des espaces réservés en suivant les modifications du nombre de caractères de la vue texte.

Swift 3:

class NotesViewController : UIViewController, UITextViewDelegate {

    @IBOutlet var textView : UITextView!
    var placeholderLabel : UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        textView.delegate = self
        placeholderLabel = UILabel()
        placeholderLabel.text = "Enter some text..."
        placeholderLabel.font = UIFont.italicSystemFont(ofSize: (textView.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        textView.addSubview(placeholderLabel)
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.isHidden = !textView.text.isEmpty
    }

    func textViewDidChange(_ textView: UITextView) {
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

Swift 2: Idem, sauf: italicSystemFontOfSize(textView.font.pointSize),UIColor.lightGrayColor



19
Cette méthode est indéniablement la plus simple et la moins sujette aux erreurs que j'ai trouvée. Fabuleux.
David

6
C'est une bonne méthode, mais le texte de l'étiquette peut sortir des limites si le texte de l'espace réservé est très long.
Aks

5
Simple à utiliser et s'intègre parfaitement au comportement existant. Le message d'espace réservé ne devrait pas être si verbeux de toute façon, mais le fait de définir les lignes sur 0 ne résoudrait-il pas ce problème?
Tommie C.

2
Je préfère généralement cette méthode, bien qu'elle soit problématique si le texte est aligné au centre, car le curseur sera centré au-dessus de l'espace réservé au lieu d'être à gauche de celui-ci.
blwinters

1
@blwinters - Ce serait un cas d'angle vraiment inhabituel, n'est-ce pas? Mais en supposant qu'il s'agit d'un champ de saisie sur plusieurs lignes dans ce cas (je dois le supposer), ne pourriez-vous pas simplement ajuster le calcul du décalage vertical pour éloigner un peu le texte de l'espace réservé du curseur?
clearlight

35

Je recommande fortement d'utiliser la bibliothèque KMPlaceholderTextView . Très simple à utiliser.


1
C'est de loin la solution la plus simple (n'implique pas d'ajouter des UILabels ou de faire des choses spéciales pour les délégués). Fonctionne hors de la boîte.
HixField

Ça devrait marcher. L'auteur a déclaré qu'il prend en charge Swift 3.0+
t4nhpt

27

Rapide:

Ajoutez votre vue texte par programme ou via Interface Builder, si le dernier, créez la sortie:

@IBOutlet weak var yourTextView: UITextView!

Veuillez ajouter le délégué (UITextViewDelegate):

class ViewController: UIViewController, UITextViewDelegate {

Dans la méthode viewDidLoad, ajoutez les éléments suivants:

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    yourTextView.delegate = self
    yourTextView.text = "Placeholder text goes right here..."
    yourTextView.textColor = UIColor.lightGray

Maintenant, laissez-moi vous présenter la partie magique, ajoutez cette fonction:

func textViewDidBeginEditing(_ textView: UITextView) {

    if yourTextView.textColor == UIColor.lightGray {
        yourTextView.text = ""
        yourTextView.textColor = UIColor.black
    }
}

Notez que cela s'exécutera chaque fois que l'édition commencera, nous vérifierons les conditions pour indiquer l'état, en utilisant la propriété color. Définition du texte surnil je ne recommande pas. Juste après cela, nous avons défini la couleur du texte sur la couleur souhaitée, dans ce cas, le noir.

Maintenant, ajoutez également cette fonction:

func textViewDidEndEditing(_ textView: UITextView) {

    if yourTextView.text == "" {

        yourTextView.text = "Placeholder text ..."
        yourTextView.textColor = UIColor.lightGray
    }
}

Permettez-moi d'insister, ne comparez pas à nil, j'ai déjà essayé cela et cela ne fonctionnerait pas. Nous redéfinissons ensuite les valeurs sur le style de l'espace réservé et redéfinissons la couleur sur la couleur de l'espace réservé car c'est une condition pour l'archivage textViewDidBeginEditing.


16

Utilisez cette extension, c'est la meilleure façon de définir un espace réservé dans UITextView. Mais assurez-vous d'avoir attaché des délégués à TextView. Vous pouvez définir un espace réservé comme ceci: -

yourTextView.placeholder = "Placeholder" 

extension UITextView :UITextViewDelegate
{

    /// Resize the placeholder when the UITextView bounds change
    override open var bounds: CGRect {
        didSet {
            self.resizePlaceholder()
        }
    }

    /// The UITextView placeholder text
    public var placeholder: String? {
        get {
            var placeholderText: String?

            if let placeholderLabel = self.viewWithTag(100) as? UILabel {
                placeholderText = placeholderLabel.text
            }

            return placeholderText
        }
        set {
            if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
                placeholderLabel.text = newValue
                placeholderLabel.sizeToFit()
            } else {
                self.addPlaceholder(newValue!)
            }
        }
    }

    /// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
    ///
    /// - Parameter textView: The UITextView that got updated
    public func textViewDidChange(_ textView: UITextView) {
        if let placeholderLabel = self.viewWithTag(100) as? UILabel {
            placeholderLabel.isHidden = self.text.characters.count > 0
        }
    }

    /// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
    private func resizePlaceholder() {
        if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
            let labelX = self.textContainer.lineFragmentPadding
            let labelY = self.textContainerInset.top - 2
            let labelWidth = self.frame.width - (labelX * 2)
            let labelHeight = placeholderLabel.frame.height

            placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
        }
    }

    /// Adds a placeholder UILabel to this UITextView
    private func addPlaceholder(_ placeholderText: String) {
        let placeholderLabel = UILabel()

        placeholderLabel.text = placeholderText
        placeholderLabel.sizeToFit()

        placeholderLabel.font = self.font
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.tag = 100

        placeholderLabel.isHidden = self.text.characters.count > 0

        self.addSubview(placeholderLabel)
        self.resizePlaceholder()
        self.delegate = self
    }
}

1
Fonctionné comme des charmes !! Merci beaucoup ! :)
Nahid Raihan

Bienvenue cher tout le meilleur
Sandip Gill

15

Je suis surpris que personne n'ait mentionné NSTextStorageDelegate. UITextViewDelegateLes méthodes de ne seront déclenchées que par l'interaction de l'utilisateur, mais pas par programme. Par exemple, lorsque vous définissez une vue de textetext propriété d' programme, vous devrez définir vous-même la visibilité de l'espace réservé, car les méthodes déléguées ne seront pas appelées.

Cependant, avec NSTextStorageDelegatela textStorage(_:didProcessEditing:range:changeInLength:)méthode de, vous serez averti de toute modification du texte, même si elle est effectuée par programme. Attribuez-le simplement comme ceci:

textView.textStorage.delegate = self

(Dans UITextView, cette propriété déléguée est nilpar défaut, elle n'affectera donc aucun comportement par défaut.)

Combiner avec la UILabeltechnique @clearlight montre, on peut facilement envelopper l'ensemble UITextViewde la placeholdermise en œuvre dans une extension.

extension UITextView {

    private class PlaceholderLabel: UILabel { }

    private var placeholderLabel: PlaceholderLabel {
        if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
            return label
        } else {
            let label = PlaceholderLabel(frame: .zero)
            label.font = font
            addSubview(label)
            return label
        }
    }

    @IBInspectable
    var placeholder: String {
        get {
            return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
        }
        set {
            let placeholderLabel = self.placeholderLabel
            placeholderLabel.text = newValue
            placeholderLabel.numberOfLines = 0
            let width = frame.width - textContainer.lineFragmentPadding * 2
            let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
            placeholderLabel.frame.size.height = size.height
            placeholderLabel.frame.size.width = width
            placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)

            textStorage.delegate = self
        }
    }

}

extension UITextView: NSTextStorageDelegate {

    public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
        if editedMask.contains(.editedCharacters) {
            placeholderLabel.isHidden = !text.isEmpty
        }
    }

}

Notez que l'utilisation d'une classe privée (imbriquée) appelée PlaceholderLabel. Il n'a aucune implémentation du tout, mais il nous fournit un moyen d'identifier l'étiquette d'espace réservé, qui est beaucoup plus «swifty» que d'utiliser letag propriété.

Avec cette approche, vous pouvez toujours affecter le délégué de UITextViewà quelqu'un d'autre.

Vous n'avez même pas besoin de changer les classes de vos vues de texte. Ajoutez simplement les extensions et vous pourrez attribuer une chaîne d'espace réservé à chaque élément de UITextViewvotre projet, même dans Interface Builder.

J'ai omis l'implémentation d'une placeholderColorpropriété pour des raisons de clarté, mais elle peut être implémentée pour quelques lignes de plus avec une variable calculée similaire à placeholder.


Solution très élégante. J'aime cela.
Dave Batton

textView.textStorage.delegate = selfcela dans un contrôleur de vue nous obligera à lier ce contrôleur de vue avec NSTextStorageDelegate. C'est vraiment nécessaire?
Hemang

Simple et fonctionne comme un charme. Cette réponse doit être acceptée
Qaiser Abbas

@Hemang, c'est la vue texte elle-même NSTextStorageDelegate, pas le contrôleur de vue.
yesleon

14

Je l'ai fait en utilisant deux vues de texte différentes:

  1. Un en arrière-plan utilisé comme espace réservé.
  2. Un au premier plan (avec un fond transparent) que l'utilisateur tape réellement.

L'idée est qu'une fois que l'utilisateur commence à taper des éléments dans la vue de premier plan, l'espace réservé à l'arrière-plan disparaît (et réapparaît si l'utilisateur supprime tout). Il se comporte donc exactement comme un espace réservé pour le champ de texte sur une seule ligne.

Voici le code que j'ai utilisé pour cela. Notez que descriptionField est le champ dans lequel l'utilisateur tape et descriptionPlaceholder est celui en arrière-plan.

func textViewDidChange(descriptionField: UITextView) {
    if descriptionField.text.isEmpty == false {
        descriptionPlaceholder.text = ""
    } else {
        descriptionPlaceholder.text = descriptionPlaceholderText
    }
}

1
De cette façon, c'est un peu hacky mais c'est de loin le plus facile et génère exactement les résultats que vous souhaitez. Bonne idée
William T.

9

Sur la base de certaines des excellentes suggestions ici déjà, j'ai pu rassembler la sous-classe légère et compatible avec Interface-Builder suivante UITextView, qui:

  • Comprend un texte d'espace réservé configurable, conçu comme celui de UITextField.
  • Ne nécessite aucune sous-vue ou contrainte supplémentaire.
  • Ne nécessite aucune délégation ou autre comportement du ViewController.
  • Ne nécessite aucune notification.
  • Garde ce texte complètement séparé de toutes les classes extérieures qui regardent la textpropriété du champ .

Toutes les suggestions d'amélioration sont les bienvenues, surtout s'il existe un moyen de tirer la couleur de l'espace réservé d'iOS par programme, plutôt que de la coder en dur.

Swift v5:

import UIKit
@IBDesignable class TextViewWithPlaceholder: UITextView {

    override var text: String! { // Ensures that the placeholder text is never returned as the field's text
        get {
            if showingPlaceholder {
                return "" // When showing the placeholder, there's no real text to return
            } else { return super.text }
        }
        set { super.text = newValue }
    }
    @IBInspectable var placeholderText: String = ""
    @IBInspectable var placeholderTextColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0) // Standard iOS placeholder color (#C7C7CD). See /programming/31057746/whats-the-default-color-for-placeholder-text-in-uitextfield
    private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder

    override func didMoveToWindow() {
        super.didMoveToWindow()
        if text.isEmpty {
            showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
        }
    }

    override func becomeFirstResponder() -> Bool {
        // If the current text is the placeholder, remove it
        if showingPlaceholder {
            text = nil
            textColor = nil // Put the text back to the default, unmodified color
            showingPlaceholder = false
        }
        return super.becomeFirstResponder()
    }

    override func resignFirstResponder() -> Bool {
        // If there's no text, put the placeholder back
        if text.isEmpty {
            showPlaceholderText()
        }
        return super.resignFirstResponder()
    }

    private func showPlaceholderText() {
        showingPlaceholder = true
        textColor = placeholderTextColor
        text = placeholderText
    }
}

6

SET value in view load

    txtVw!.autocorrectionType = UITextAutocorrectionType.No
    txtVw!.text = "Write your Placeholder"
    txtVw!.textColor = UIColor.lightGrayColor()



func textViewDidBeginEditing(textView: UITextView) {
    if (txtVw?.text == "Write your Placeholder")

    {
        txtVw!.text = nil
        txtVw!.textColor = UIColor.blackColor()
    }
}

func textViewDidEndEditing(textView: UITextView) {
    if txtVw!.text.isEmpty
    {
        txtVw!.text = "Write your Placeholder"
        txtVw!.textColor = UIColor.lightGrayColor()
    }
    textView.resignFirstResponder()
}

6

J'ai essayé de rendre le code pratique de clearlight de réponse .

extension UITextView{

    func setPlaceholder() {

        let placeholderLabel = UILabel()
        placeholderLabel.text = "Enter some text..."
        placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        placeholderLabel.tag = 222
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.isHidden = !self.text.isEmpty

        self.addSubview(placeholderLabel)
    }

    func checkPlaceholder() {
        let placeholderLabel = self.viewWithTag(222) as! UILabel
        placeholderLabel.isHidden = !self.text.isEmpty
    }

}

usage

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

func textViewDidChange(_ textView: UITextView) {
    textView.checkPlaceholder()
}

Quelques problèmes. (1) En tant qu'extension qui suppose et `` emprunte '' une valeur de propriété de balise UIView, il existe un risque que quelqu'un utilise la même balise dans sa propre hiérarchie de vues, ignorant l'utilisation de l'extension, créant un bogue extrêmement difficile à diagnostiquer. Des choses comme ça n'appartiennent pas au code de bibliothèque ou aux extensions. (2) Il exige toujours que l'appelant déclare un délégué. L' espace flottant évite les piratages, a une petite empreinte, est simple et entièrement local, ce qui en fait une valeur sûre.
clearlight

5

Une autre solution (Swift 3):

import UIKit

protocol PlaceholderTextViewDelegate {
    func placeholderTextViewDidChangeText(_ text:String)
    func placeholderTextViewDidEndEditing(_ text:String)
}

final class PlaceholderTextView: UITextView {

    var notifier:PlaceholderTextViewDelegate?

    var placeholder: String? {
        didSet {
            placeholderLabel?.text = placeholder
        }
    }
    var placeholderColor = UIColor.lightGray
    var placeholderFont = UIFont.appMainFontForSize(14.0) {
        didSet {
            placeholderLabel?.font = placeholderFont
        }
    }

    fileprivate var placeholderLabel: UILabel?

    // MARK: - LifeCycle

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

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

    override func awakeFromNib() {
        super.awakeFromNib()

        self.delegate = self
        NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)

        placeholderLabel = UILabel()
        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.text = placeholder
        placeholderLabel?.textAlignment = .left
        placeholderLabel?.numberOfLines = 0
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        placeholderLabel?.font = placeholderFont

        var height:CGFloat = placeholderFont.lineHeight
        if let data = placeholderLabel?.text {

            let expectedDefaultWidth:CGFloat = bounds.size.width
            let fontSize:CGFloat = placeholderFont.pointSize

            let textView = UITextView()
            textView.text = data
            textView.font = UIFont.appMainFontForSize(fontSize)
            let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
                                                               height: CGFloat.greatestFiniteMagnitude))
            let expectedTextViewHeight = sizeForTextView.height

            if expectedTextViewHeight > height {
                height = expectedTextViewHeight
            }
        }

        placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)

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

    func textDidChangeHandler(notification: Notification) {
        layoutSubviews()
    }

}

extension PlaceholderTextView : UITextViewDelegate {
    // MARK: - UITextViewDelegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if(text == "\n") {
            textView.resignFirstResponder()
            return false
        }
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        notifier?.placeholderTextViewDidChangeText(textView.text)
    }

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

résultat

entrez la description de l'image ici


4

Une solution simple et rapide qui fonctionne pour moi est:

@IBDesignable
class PlaceHolderTextView: UITextView {

    @IBInspectable var placeholder: String = "" {
         didSet{
             updatePlaceHolder()
        }
    }

    @IBInspectable var placeholderColor: UIColor = UIColor.gray {
        didSet {
            updatePlaceHolder()
        }
    }

    private var originalTextColor = UIColor.darkText
    private var originalText: String = ""

    private func updatePlaceHolder() {

        if self.text == "" || self.text == placeholder  {

            self.text = placeholder
            self.textColor = placeholderColor
            if let color = self.textColor {

                self.originalTextColor = color
            }
            self.originalText = ""
        } else {
            self.textColor = self.originalTextColor
            self.originalText = self.text
        }

    }

    override func becomeFirstResponder() -> Bool {
        let result = super.becomeFirstResponder()
        self.text = self.originalText
        self.textColor = self.originalTextColor
        return result
    }
    override func resignFirstResponder() -> Bool {
        let result = super.resignFirstResponder()
        updatePlaceHolder()

        return result
    }
}

4

Voici ce que j'utilise pour ce travail.

@IBDesignable class UIPlaceholderTextView: UITextView {

    var placeholderLabel: UILabel?

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

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

    override func prepareForInterfaceBuilder() {
        sharedInit()
    }

    func sharedInit() {
        refreshPlaceholder()
        NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
    }

    @IBInspectable var placeholder: String? {
        didSet {
            refreshPlaceholder()
        }
    }

    @IBInspectable var placeholderColor: UIColor? = .darkGray {
        didSet {
            refreshPlaceholder()
        }
    }

    @IBInspectable var placeholderFontSize: CGFloat = 14 {
        didSet {
            refreshPlaceholder()
        }
    }

    func refreshPlaceholder() {
        if placeholderLabel == nil {
            placeholderLabel = UILabel()
            let contentView = self.subviews.first ?? self

            contentView.addSubview(placeholderLabel!)
            placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false

            placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
            placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
            placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
            placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom)
        }
        placeholderLabel?.text = placeholder
        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
    }

    @objc func textChanged() {
        if self.placeholder?.isEmpty ?? true {
            return
        }

        UIView.animate(withDuration: 0.25) {
            if self.text.isEmpty {
                self.placeholderLabel?.alpha = 1.0
            } else {
                self.placeholderLabel?.alpha = 0.0
            }
        }
    }

    override var text: String! {
        didSet {
            textChanged()
        }
    }

}

Je sais qu'il existe plusieurs approches similaires à celle-ci, mais les avantages de celle-ci sont:

  • Peut définir le texte de l'espace réservé, la taille de la police et la couleur dans IB .
  • N'affiche plus l'avertissement de " Scroll View a ambiguous scrollable content " dans IB.
  • Ajoutez une animation pour afficher / masquer l'espace réservé.

3

Swift 3.2

extension EditProfileVC:UITextViewDelegate{

    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.lightGray {
            textView.text = nil
            textView.textColor = UIColor.black
       }
    }
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty {
            textView.text = "Placeholder"
            textView.textColor = UIColor.lightGray
        }
    }
}

Tout d'abord, lorsque l'utilisateur commence à modifier l'appel textViewDidBeginEditing, puis vérifiez si la couleur du texte gris signifie que l'utilisateur n'a rien écrit, définissez-la comme textview nil et changez la couleur en noir pour les SMS de l'utilisateur.

Lorsque l'utilisateur termine l'édition textViewDidEndEditing est appel et vérifier si l'utilisateur n'écrit rien dans textview puis le texte défini en couleur grise avec le texte "PlaceHolder"


3

Voici ma façon de résoudre ce problème ( Swift 4 ):

L'idée était de faire la solution la plus simple possible qui permet d'utiliser des espaces réservés de différentes couleurs, se redimensionne à la taille des espaces réservés, n'écrase pas en delegateattendant de garder toutes les UITextViewfonctions fonctionnent comme prévu.

import UIKit

class PlaceholderTextView: UITextView {
    var placeholderColor: UIColor = .lightGray
    var defaultTextColor: UIColor = .black

    private var isShowingPlaceholder = false {
        didSet {
            if isShowingPlaceholder {
                text = placeholder
                textColor = placeholderColor
            } else {
                textColor = defaultTextColor
            }
        }
    }

    var placeholder: String? {
        didSet {
            isShowingPlaceholder = !hasText
        }
    }

    @objc private func textViewDidBeginEditing(notification: Notification) {
        textColor = defaultTextColor
        if isShowingPlaceholder { text = nil }
    }

    @objc private func textViewDidEndEditing(notification: Notification) {
        isShowingPlaceholder = !hasText
    }

    // MARK: - Construction -
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        setup()
    }

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

    private func setup() {
        NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
    }

    // MARK: - Destruction -
    deinit { NotificationCenter.default.removeObserver(self) }
}

Il s'agit d'une excellente solution simple.
JaredH

2

Je ne sais pas pourquoi les gens compliquent trop ce problème ... C'est assez simple et simple. Voici une sous-classe de UITextView qui fournit les fonctionnalités demandées.

- (void)customInit
{
    self.contentMode = UIViewContentModeRedraw;
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}

    - (void)textChanged:(NSNotification *)notification
    {
        if (notification.object == self) {
            if(self.textStorage.length != 0 || !self.textStorage.length) {
                [self setNeedsDisplay];
            }
        }
    }


    #pragma mark - Setters

    - (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
    {
        self.placeholderText = placeholderText;
        self.placeholderTextFont = font;

    }



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

        if (self.textStorage.length != 0) {
            return;
        }

        CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
        NSDictionary *attributes =  @{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
        [self.placeholderText drawInRect:inset withAttributes:attributes];
    }`

Pour info, avant que quelqu'un ne me batte ... c'est assez direct à traduire en Swift. Rien de compliqué ici.
TheCodingArt

2

Ceci est ma solution prête à l'emploi si vous travaillez avec plusieurs vues de texte

func textViewShouldBeginEditing(textView: UITextView) -> Bool {        
    // Set cursor to the beginning if placeholder is set
    if textView.textColor == UIColor.lightGrayColor() {
        textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
    }

    return true
}

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    // Remove placeholder
    if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
        textView.text = ""
        textView.textColor = UIColor.blackColor()
    }

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

    return true
}

func textViewDidChange(textView: UITextView) {
    // Set placeholder if text is empty
    if textView.text.isEmpty {
        textView.text = NSLocalizedString("Hint", comment: "hint")
        textView.textColor = UIColor.lightGrayColor()
        textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
    }
}

func textViewDidChangeSelection(textView: UITextView) {
    // Set cursor to the beginning if placeholder is set
    let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)

    // Do not change position recursively
    if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
        textView.selectedTextRange = firstPosition
    }
}

C'est vraiment bien!
KevinVuD

2

Swift 3.1

Cette extension a bien fonctionné pour moi: https://github.com/devxoul/UITextView-Placeholder

Voici un extrait de code:

Installez-le via pod:

pod 'UITextView+Placeholder', '~> 1.2'

Importez-le dans votre classe

import UITextView_Placeholder

Et ajoutez une placeholderpropriété à votre déjà crééUITextView

textView.placeholder = "Put some detail"

C'est ça ... Voici à quoi ça ressemble (la troisième case est a UITextView) entrez la description de l'image ici


2

Contrairement à peu près toutes les réponses à ce poste, n'ont une propriété de l' espace réservé. Pour des raisons au-delà de ma compréhension, il n'est exposé qu'en IB, en tant que tel:UITextView

<userDefinedRuntimeAttributes>
  <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="My Placeholder"/>
</userDefinedRuntimeAttributes>

Donc, si vous utilisez des storyboards et qu'un espace réservé statique suffit, définissez simplement la propriété sur l'inspecteur.

Vous pouvez également définir cette propriété dans un code comme celui-ci:

textView.setValue("My Placeholder", forKeyPath: "placeholder")

Son nuageux quant à la météo est accessible via une API privée, car la propriété est exposée.

Je n'ai pas essayé de soumettre avec cette méthode. Mais je soumettrai cette façon sous peu et mettrai à jour cette réponse en conséquence.

METTRE À JOUR:

J'ai expédié ce code dans plusieurs versions sans problème d'Apple.

MISE À JOUR: Cela ne fonctionnera que dans Xcode pre 11.2


dans Swift 5, vous pouvez écrire myTextView, placeholder = "enter your eye color"
user462990

@ user462990 Pouvez-vous fournir un lien de documentation pour cela? Je ne pense pas que ce soit exact.
Alex Chase

désolé, je n'ai pas trouvé dox mais c'est vraiment facile à tester ... par exemple "alert.addTextField {(textField3) dans textField3.placeholder = language.JBLocalize (phrase:" yourName ")}. jbLocalize est un gestionnaire de traduction qui retourne un String
user462990

4
@ user462990 Vous faites référence à UITextFieldPAS UITextView s'il vous plaît lire les questions / réponses plus attentivement.
Alex Chase

3
Excellente solution, mais ne fonctionne pas pour moi. D'une manière ou d'une autre, il se bloque toujours. Pourriez-vous spécifier les versions de cible de déploiement, swift et xCode que vous utilisez?
T. Pasichnyk

1

Il n'y a pas une telle propriété dans ios pour ajouter un espace réservé directement dans TextView, vous pouvez plutôt ajouter une étiquette et afficher / masquer le changement dans textView. SWIFT 2.0 et assurez-vous d'implémenter le textviewdelegate

func textViewDidChange(TextView: UITextView)
{

 if  txtShortDescription.text == ""
    {
        self.lblShortDescription.hidden = false
    }
    else
    {
        self.lblShortDescription.hidden = true
    }

}

1

Swift - J'ai écrit une classe qui a hérité d'UITextView et j'ai ajouté un UILabel en tant que sous-vue pour agir comme un espace réservé.

  import UIKit
  @IBDesignable
  class HintedTextView: UITextView {

      @IBInspectable var hintText: String = "hintText" {
          didSet{
              hintLabel.text = hintText
          }
      }

      private lazy var hintLabel: UILabel = {
          let label = UILabel()
          label.font = UIFont.systemFontOfSize(16)
          label.textColor = UIColor.lightGrayColor()
          label.translatesAutoresizingMaskIntoConstraints = false
          return label
      }()


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

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

      override func prepareForInterfaceBuilder() {
         super.prepareForInterfaceBuilder()
         setupView()
      }

      private func setupView() {

        translatesAutoresizingMaskIntoConstraints = false
        delegate = self
        font = UIFont.systemFontOfSize(16)

        addSubview(hintLabel)

        NSLayoutConstraint.activateConstraints([

           hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
           hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
           hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
           hintLabel.heightAnchor.constraintEqualToConstant(30)
         ])
        }

      override func layoutSubviews() {
        super.layoutSubviews()
        setupView()
     }

}

Excellent exemple d'utilisation de contraintes pour positionner l'espace réservé, mais la position idéale de l'espace réservé est juste au-dessus de l'endroit où le premier caractère du texte va aller, donc le calcul de cette position en fonction de la police de la vue texte est préférable pour cela car il s'adapte quelle que soit la taille de police définie pour la vue texte.
clearlight

1

J'aime la solution de @ nerdist. Sur cette base, j'ai créé une extension pour UITextView:

import Foundation
import UIKit

extension UITextView
{
  private func add(_ placeholder: UILabel) {
    for view in self.subviews {
        if let lbl = view as? UILabel  {
            if lbl.text == placeholder.text {
                lbl.removeFromSuperview()
            }
        }
    }
    self.addSubview(placeholder)
  }

  func addPlaceholder(_ placeholder: UILabel?) {
    if let ph = placeholder {
      ph.numberOfLines = 0  // support for multiple lines
      ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
      ph.sizeToFit()
      self.add(ph)
      ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
      ph.textColor = UIColor(white: 0, alpha: 0.3)
      updateVisibility(ph)
    }
  }

  func updateVisibility(_ placeHolder: UILabel?) {
    if let ph = placeHolder {
      ph.isHidden = !self.text.isEmpty
    }
  }
}

Dans une classe ViewController, par exemple, voici comment je l'utilise:

class MyViewController: UIViewController, UITextViewDelegate {
  private var notePlaceholder: UILabel!
  @IBOutlet weak var txtNote: UITextView!
  ...
  // UIViewController
  override func viewDidLoad() {
    notePlaceholder = UILabel()
    notePlaceholder.text = "title\nsubtitle\nmore..."
    txtNote.addPlaceholder(notePlaceholder)
    ...
  }

  // UITextViewDelegate
  func textViewDidChange(_ textView: UITextView) {
    txtNote.updateVisbility(notePlaceholder)
    ...
  }

Placeholder sur UITextview!

entrez la description de l'image ici

MISE À JOUR :

Si vous modifiez le texte de textview dans le code, n'oubliez pas d'appeler la méthode updateVisibitly pour masquer l'espace réservé:

txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.

Pour empêcher l'ajout d'espace réservé plus d'une fois, une add()fonction privée est ajoutée extension.


Merci encore pour votre amélioration par rapport à l'original. J'ai passé trop de temps ce week-end, mais je pense que vous apprécierez la variante extrême d'EZ Placeholder que j'ai finalement trouvée : stackoverflow.com/a/41081244/2079103 (je vous ai rendu hommage pour avoir contribué à l'évolution de la solutions d'espace réservé)
clearlight

1

Dans swift2.2:

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

}

Dans swift3:

import UIKit

classe 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() {
    NotificationCenter.default.addObserver(self,
                                                     selector: #selector(textDidChange),
                                                     name: NSNotification.Name.UITextViewTextDidChange,
                                                     object: nil)

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

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

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

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

deinit {
    NotificationCenter.default.removeObserver(self,
                                                        name: NSNotification.Name.UITextViewTextDidChange,
                                                        object: nil)
}

}

J'ai écrit un cours en swift. Vous devez importer cette classe chaque fois que nécessaire.


Veuillez indiquer à quelle version de Swift il s'agit (ne fonctionne pas pour Swift 3)
clearlight

1

Je ne peux pas ajouter de commentaire à cause de la réputation. ajouter un besoin de délégué supplémentaire dans la réponse @clearlight.

func textViewDidBeginEditing(_ textView: UITextView) { 
        cell.placeholderLabel.isHidden = !textView.text.isEmpty
}

est besoin

parce textViewDidChangequ'on ne l'appelle pas la première fois


1

non, aucun espace réservé n'est disponible pour l'affichage de texte. vous devez mettre une étiquette au-dessus lorsque l'utilisateur entre dans textview, puis la masquer ou la définir par défaut lorsque l'utilisateur entre supprimer toutes les valeurs.


1

func setPlaceholder(){
var placeholderLabel = UILabel()
        placeholderLabel.text = "Describe your need..."
        placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0)
        placeholderLabel.sizeToFit()
        descriptionTextView.addSubview(placeholderLabel)
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
}



//Delegate Method.

func textViewDidChange(_ textView: UITextView) {
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
	


1

J'ai dû répartir la file d'attente pour que mon texte d'espace réservé réapparaisse une fois l'édition terminée.

func textViewDidBeginEditing(_ textView: UITextView) {

    if textView.text == "Description" {
        textView.text = nil
    }
}

func textViewDidEndEditing(_ textView: UITextView) {

    if textView.text.isEmpty {
        DispatchQueue.main.async {
            textView.text = "Description"
        }
    }
}

Que dois-je taper au lieu de la dernière ligne ”textView.text =” Description ”si je veux que cette valeur soit ce que l'utilisateur tape?
ISS

1

Rapide:

Ajoutez votre TextView @IBOutlet:

@IBOutlet weak var txtViewMessage: UITextView!

Dans la viewWillAppearméthode, ajoutez les éléments suivants:

override func viewWillAppear(_ animated: Bool)
{
    super.viewWillAppear(animated)

    txtViewMessage.delegate = self    // Give TextViewMessage delegate Method

    txtViewMessage.text = "Place Holder Name"
    txtViewMessage.textColor = UIColor.lightGray

}

Veuillez ajouter l' Delegateextension Using (UITextViewDelegate):

// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate
{

    func textViewDidBeginEditing(_ textView: UITextView)
    {

        if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name"
        {
            txtViewMessage.text = ""
            txtViewMessage.textColor = UIColor.black
        }
    }

    func textViewDidEndEditing(_ textView: UITextView)
    {

        if txtViewMessage.text.isEmpty
        {
            txtViewMessage.text = "Place Holder Name"
            txtViewMessage.textColor = UIColor.lightGray
        }
    }
}

1

Notre solution évite de nettoyer les propriétés UITextView textet textColor, ce qui est pratique si vous maintenez un compteur de caractères.

C'est simple:

1) Créez un mannequin UITextViewdans Storyboard avec les mêmes propriétés que le maître UITextView. Attribuez un texte d'espace réservé au texte factice.

2) Alignez les bords supérieur, gauche et droit des deux UITextViews.

3) Placez le mannequin derrière le maître.

4) Remplacez la textViewDidChange(textView:)fonction de délégué du maître et montrez le mannequin si le maître a 0 caractères. Sinon, montrez le maître.

Cela suppose que les deux UITextViewsont des arrière-plans transparents. Si ce n'est pas le cas, placez le mannequin sur le dessus quand il y a 0 caractères et poussez-le en dessous quand il y a> 0 caractères. Vous devrez également permuter les répondeurs pour vous assurer que le curseur suit la droite UITextView.


1

Swift 4, 4.2 et 5

[![@IBOutlet var detailTextView: UITextView!

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

extension ContactUsViewController : UITextViewDelegate {

    public func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.text == "Write your message here..." {
            detailTextView.text = ""
            detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.86)

        }
        textView.becomeFirstResponder()
    }

    public func textViewDidEndEditing(_ textView: UITextView) {

        if textView.text == "" {
            detailTextView.text = "Write your message here..."
            detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.30)
        }
        textView.resignFirstResponder()
    }
[![}][1]][1]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.