Avatar ·

Как добавить текст-плейсхолдер в UITextView на Swift?

Всем привет! Я начинающий iOS-разработчик, пишу на Swift. Столкнулся с проблемой: в UITextField есть встроенное свойство `placeholder`, которое показывает серый текст-подсказку, пока поле пустое. Но в UITextView такого свойства нет. Мне нужно сделать поле для ввода многострочного текста (например, для комментария или описания), чтобы, когда пользователь ещё ничего не ввёл, отображалась подсказка "Введите ваш текст...". Как только пользователь начинает печатать, плейсхолдер должен исчезать. Если пользователь удаляет весь текст, плейсхолдер должен снова появиться. Я пробовал использовать `UILabel`, который кладу поверх UITextView, но он не исчезает, когда я начинаю печатать, или остаётся видимым, если текст пустой. Вот мой текущий код: ```swift import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! let placeholderLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() placeholderLabel.text = "Введите ваш текст..." placeholderLabel.font = UIFont.systemFont(ofSize: 16) placeholderLabel.textColor = UIColor.lightGray placeholderLabel.sizeToFit() textView.addSubview(placeholderLabel) placeholderLabel.frame.origin = CGPoint(x: 5, y: textView.font?.pointSize ?? 16 / 2) } } ``` Проблема в том, что лейбл не синхронизируется с текстом. Когда я начинаю печатать, он остаётся. И если я удаляю текст, он не появляется снова. Как правильно реализовать плейсхолдер для UITextView? Подскажите лучший способ. Заранее спасибо! <<>>

Лучший способ: использовать делегат UITextView и UILabel

Проблема в вашем коде — вы не подписываетесь на изменения текста в UITextView. Вот правильное и надёжное решение, которое используют в реальных проектах.

Шаг 1: Создайте плейсхолдер-лейбл

Добавьте UILabel как дочерний элемент UITextView. Важно: лейбл должен быть неинтерактивным (isUserInteractionEnabled = false), чтобы он не перехватывал касания.

Шаг 2: Реализуйте делегат UITextViewDelegate

Подпишитесь на изменения текста через метод textViewDidChange. В нём проверяйте, пустой ли текст, и скрывайте/показывайте плейсхолдер.

Готовый код

import UIKit

class ViewController: UIViewController, UITextViewDelegate {
    
    @IBOutlet weak var textView: UITextView!
    let placeholderLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Настройка textView
        textView.delegate = self
        textView.font = UIFont.systemFont(ofSize: 16)
        textView.textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4)
        
        // Настройка плейсхолдера
        placeholderLabel.text = "Введите ваш текст..."
        placeholderLabel.font = textView.font
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.numberOfLines = 0 // поддержка многострочного текста
        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        placeholderLabel.isUserInteractionEnabled = false // важно!
        
        textView.addSubview(placeholderLabel)
        
        // Auto Layout: привязываем лейбл к textView с учётом отступов
        NSLayoutConstraint.activate([
            placeholderLabel.topAnchor.constraint(equalTo: textView.topAnchor, constant: textView.textContainerInset.top + 2),
            placeholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: textView.textContainerInset.left + 4),
            placeholderLabel.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: -textView.textContainerInset.right - 4)
        ])
        
        // Начальное состояние: показываем плейсхолдер
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
    
    // MARK: - UITextViewDelegate
    func textViewDidChange(_ textView: UITextView) {
        // Показываем плейсхолдер, если текст пустой
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

Почему это работает

  • Делегат — метод textViewDidChange вызывается при каждом изменении текста (ввод, удаление, вставка).
  • Auto Layout — лейбл автоматически подстраивается под размеры textView, даже если меняются отступы или шрифт.
  • isUserInteractionEnabled = false — пользователь может тапать по плейсхолдеру, и касание уйдёт в UITextView, открывая клавиатуру.
  • numberOfLines = 0 — если плейсхолдер длинный, он будет переноситься на несколько строк.

Дополнительные улучшения

Если вы используете Storyboard, можно создать подкласс UITextView и добавить плейсхолдер как свойство @IBInspectable. Это сделает компонент переиспользуемым:

class PlaceholderTextView: UITextView {
    
    private let placeholderLabel = UILabel()
    
    @IBInspectable var placeholderText: String = "Введите текст..." {
        didSet { placeholderLabel.text = placeholderText }
    }
    
    @IBInspectable var placeholderColor: UIColor = .lightGray {
        didSet { placeholderLabel.textColor = placeholderColor }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        setupPlaceholder()
    }
    
    private func setupPlaceholder() {
        // ... код инициализации лейбла (как выше)
        NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
    }
    
    @objc private func textChanged() {
        placeholderLabel.isHidden = !text.isEmpty
    }
}

Такой подход удобен: вы просто перетаскиваете PlaceholderTextView в Storyboard и задаёте текст плейсхолдера в инспекторе атрибутов.

<<>>

Альтернативный ответ: Использование атрибутированного текста

Другой популярный способ — не создавать отдельный UILabel, а использовать атрибутированный текст самого UITextView. Этот метод проще в реализации, но требует аккуратной обработки.

import UIKit

class ViewController: UIViewController, UITextViewDelegate {
    
    @IBOutlet weak var textView: UITextView!
    let placeholder = "Введите ваш текст..."
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        showPlaceholder()
    }
    
    private func showPlaceholder() {
        textView.text = placeholder
        textView.textColor = UIColor.lightGray
    }
    
    private func hidePlaceholder() {
        textView.text = ""
        textView.textColor = UIColor.black // или ваш основной цвет
    }
    
    // MARK: - UITextViewDelegate
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.lightGray {
            hidePlaceholder()
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty {
            showPlaceholder()
        }
    }
}

Плюсы и минусы

  • + Очень простой код, не нужно создавать дополнительные вью.
  • + Работает с любыми шрифтами и отступами "из коробки".
  • - Текст плейсхолдера будет отправлен с формой, если вы не проверите цвет. Обязательно фильтруйте ввод: if textView.textColor != .lightGray { /* отправляем */ }.
  • - Пользователь может изменить цвет текста или удалить плейсхолдер частично. Это ломает логику.
  • - Не подходит, если нужно поддерживать стилизованный текст (NSAttributedString) — они перезапишут плейсхолдер.

Этот метод хорош для простых форм, где плейсхолдер — короткая фраза. Для продакшена я рекомендую первый способ с UILabel — он надёжнее и не влияет на содержимое textView.