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?
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?
Réponses:
Mis à jour pour Swift 4
UITextView
n'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 UITextViewDelegate
mé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 = self
pour 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 UITextView
pour contenir le texte d'espace réservé et définissez-le sur une couleur gris clair pour imiter l'apparence du UITextField
texte d'espace réservé d'un. Faites-le dans la viewDidLoad
ou 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
UITextViewDelegate
mé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 textViewDidChangeSelection
pour empêcher l'utilisateur de changer la position du curseur alors que l'espace réservé est visible. (Remarque: textViewDidChangeSelection
est 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)
}
}
}
yourTextField.delegate = self
. Si vous ne le faites pas, textViewDidBeginEditing
les textViewDidEndEditing
fonctions et ne fonctionneront pas.
Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')
.
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.isEmpty
ligne en if (updatedText?.isEmpty)! {
. Cela devrait faire l'affaire!
textView.selectedTextRange
de l'intérieur func textViewDidChangeSelection(_ textView: UITextView)
provoque une boucle infinie ...
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
Je recommande fortement d'utiliser la bibliothèque KMPlaceholderTextView . Très simple à utiliser.
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
.
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
}
}
Je suis surpris que personne n'ait mentionné NSTextStorageDelegate
. UITextViewDelegate
Les 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 NSTextStorageDelegate
la 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 nil
par défaut, elle n'affectera donc aucun comportement par défaut.)
Combiner avec la UILabel
technique @clearlight montre, on peut facilement envelopper l'ensemble UITextView
de la placeholder
mise 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 UITextView
votre projet, même dans Interface Builder.
J'ai omis l'implémentation d'une placeholderColor
proprié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
.
textView.textStorage.delegate = self
cela dans un contrôleur de vue nous obligera à lier ce contrôleur de vue avec NSTextStorageDelegate
. C'est vraiment nécessaire?
NSTextStorageDelegate
, pas le contrôleur de vue.
Je l'ai fait en utilisant deux vues de texte différentes:
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
}
}
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:
UITextField
.text
proprié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
}
}
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()
}
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()
}
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
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
}
}
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:
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"
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 delegate
attendant de garder toutes les UITextView
fonctions 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) }
}
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];
}`
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
}
}
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 placeholder
proprié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
)
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
UITextField
PAS UITextView
s'il vous plaît lire les questions / réponses plus attentivement.
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
}
}
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()
}
}
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!
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
.
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.
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 textViewDidChange
qu'on ne l'appelle pas la première fois
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.
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
}
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"
}
}
}
Rapide:
Ajoutez votre TextView
@IBOutlet
:
@IBOutlet weak var txtViewMessage: UITextView!
Dans la viewWillAppear
mé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' Delegate
extension 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
}
}
}
Notre solution évite de nettoyer les propriétés UITextView
text
et textColor
, ce qui est pratique si vous maintenez un compteur de caractères.
C'est simple:
1) Créez un mannequin UITextView
dans 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 UITextViews
ont 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
.
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]