Avatar ·

Как изменить цвет текста placeholder в UITextField с помощью Swift?

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

У меня есть UITextField с placeholder'ом. Стандартный серый цвет не подходит под дизайн моего приложения. Мне нужно, чтобы текст placeholder'а был, например, синим или красным.

Я пробовал сделать так:

textField.placeholder = "Введите имя"
textField.placeholderColor = UIColor.blue // Такого свойства нет, Xcode ругается

Потом я нашел в интернете способ с NSAttributedString:

let placeholderText = NSAttributedString(string: "Введите имя", attributes: [NSAttributedString.Key.foregroundColor: UIColor.blue])
textField.attributedPlaceholder = placeholderText

Это сработало, но мне кажется, что это слишком громоздко для такой простой задачи. К тому же, если у меня много textField'ов, придется писать это для каждого.

Подскажите, пожалуйста, есть ли более простой и элегантный способ изменить цвет placeholder'а? Может быть, есть какой-то метод или свойство, которое я упустил? Или, может быть, можно создать extension для UITextField, чтобы упростить код? Буду благодарен за любые примеры и объяснения!

Использую Swift 5, iOS 13+. Заранее спасибо!

<<>>

Решение с помощью расширения (extension) UITextField

Вы совершенно правы: каждый раз создавать NSAttributedString для каждого поля — это неудобно, особенно в больших проектах. Самый правильный и элегантный способ в Swift — добавить свойство через extension. Это позволит вам устанавливать цвет одной строкой кода.

Вот готовое решение:

import UIKit

extension UITextField {

    /// Устанавливает цвет текста placeholder для UITextField
    /// - Parameter color: Цвет, который нужно применить
    func setPlaceholderColor(_ color: UIColor) {
        // Получаем текущий текст placeholder (если он есть)
        guard let placeholder = self.placeholder else {
            // Если placeholder не задан, просто выходим
            return
        }
        
        // Создаем атрибуты с нужным цветом
        let attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: color
        ]
        
        // Применяем атрибутированную строку
        self.attributedPlaceholder = NSAttributedString(
            string: placeholder,
            attributes: attributes
        )
    }
    
    /// Устанавливает цвет и шрифт placeholder
    /// - Parameters:
    ///   - color: Цвет текста
    ///   - font: Шрифт (опционально)
    func setPlaceholderColor(_ color: UIColor, font: UIFont? = nil) {
        guard let placeholder = self.placeholder else { return }
        
        var attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: color
        ]
        
        // Если передан шрифт, добавляем его в атрибуты
        if let font = font {
            attributes[.font] = font
        }
        
        self.attributedPlaceholder = NSAttributedString(
            string: placeholder,
            attributes: attributes
        )
    }
}

Как использовать

Теперь в любом месте вашего кода вы можете написать:

// Просто меняем цвет
textField.setPlaceholderColor(.blue)

// Меняем цвет и шрифт одновременно
textField.setPlaceholderColor(.red, font: UIFont(name: "HelveticaNeue-Bold", size: 16))

// Если нужно сбросить цвет обратно на стандартный серый
textField.setPlaceholderColor(.lightGray)

Более продвинутый вариант: вычисляемое свойство

Если вы хотите, чтобы цвет можно было читать и писать как обычное свойство, можно использовать ассоциированные объекты (Associated Objects) из Objective-C runtime:

import UIKit

extension UITextField {
    
    // Ключ для хранения цвета в runtime
    private struct AssociatedKeys {
        static var placeholderColor = "placeholderColor"
    }
    
    /// Цвет текста placeholder
    @IBInspectable var placeholderColor: UIColor? {
        get {
            // Если есть attributedPlaceholder, извлекаем цвет из атрибутов
            if let attributes = self.attributedPlaceholder?.attributes(
                at: 0,
                longestEffectiveRange: nil,
                in: NSRange(location: 0, length: self.attributedPlaceholder?.length ?? 0)
            ) {
                return attributes[.foregroundColor] as? UIColor
            }
            return nil
        }
        set {
            guard let color = newValue, let placeholder = self.placeholder else { return }
            let attributes: [NSAttributedString.Key: Any] = [.foregroundColor: color]
            self.attributedPlaceholder = NSAttributedString(
                string: placeholder,
                attributes: attributes
            )
        }
    }
}

Теперь вы можете использовать это свойство в коде или прямо в Interface Builder (благодаря атрибуту @IBInspectable):

// В коде
textField.placeholderColor = UIColor.orange

// В Interface Builder: появится поле "Placeholder Color" в инспекторе атрибутов

Важные замечания

  • Порядок важен: сначала установите обычный placeholder (текст), а потом меняйте цвет. Или используйте метод, который проверяет наличие текста.
  • Не теряйте текст: если вы установите attributedPlaceholder, а потом попытаетесь прочитать placeholder — он вернет nil. Поэтому в расширении мы сначала сохраняем текст через self.placeholder.
  • Производительность: создание NSAttributedString — легковесная операция, не беспокойтесь о ней.

Этот подход считается best practice в сообществе iOS-разработчиков. Он чистый, переиспользуемый и не требует сторонних библиотек.

<<>>

Альтернативный подход: переопределение метода drawPlaceholder

Если вы не хотите использовать NSAttributedString и extension, можно переопределить метод drawPlaceholder(in:) в подклассе UITextField. Этот способ дает полный контроль над отрисовкой placeholder.

Вот пример:

import UIKit

class ColoredPlaceholderTextField: UITextField {
    
    var placeholderColor: UIColor = .lightGray {
        didSet {
            // Перерисовываем placeholder при изменении цвета
            setNeedsDisplay()
        }
    }
    
    override func drawPlaceholder(in rect: CGRect) {
        // Устанавливаем цвет и шрифт
        let attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: placeholderColor,
            .font: self.font ?? UIFont.systemFont(ofSize: 14)
        ]
        
        // Рисуем текст в заданном прямоугольнике
        let placeholderRect = rect.insetBy(dx: 0, dy: (rect.height - (self.font?.lineHeight ?? 0)) / 2)
        self.placeholder?.draw(in: placeholderRect, withAttributes: attributes)
    }
}

Плюсы:

  • Работает даже если вы не устанавливали placeholder через код (например, задали в сториборде).
  • Цвет меняется автоматически при изменении свойства.

Минусы:

  • Нужно создавать подкласс и использовать его вместо обычного UITextField.
  • Может потребоваться дополнительная настройка для разных состояний (например, при редактировании).

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