Как изменить цвет изображения (tintColor) в iOS и WatchKit с помощью Swift
Понимание tintColor и режима Template
Проблема, с которой вы столкнулись, очень распространена среди начинающих разработчиков. Дело в том, что tintColor работает только с изображениями, которые находятся в режиме Template (.alwaysTemplate). Этот режим игнорирует все цвета пикселей исходного изображения и использует только его альфа-канал (прозрачность). Цвет заливки берётся из свойства tintColor родительской вьюхи или самого UIImageView.
Размытые края и неправильное наложение цвета возникают по двум причинам:
- Изображение не является монохромным силуэтом. Если ваша иконка уже содержит цвета (например, жёлтый центр и серые края), то при применении Template режима эти цвета будут отброшены, и изображение станет сплошным силуэтом. Края могут выглядеть неровными, если исходный PNG не оптимизирован.
- Неправильный порядок установки свойств. Вы устанавливаете
tintColorдо того, как изображение переведено в Template режим. Нужно либо сначала задать rendering mode, либо использовать системный подход черезUIImageView.
Правильное решение для iOS (UIImageView)
Вот корректный способ изменить цвет изображения в iOS:
// 1. Убедитесь, что ваше изображение в Assets — это монохромный силуэт (белый или чёрный на прозрачном фоне)
let starImage = UIImage(named: "star")?.withRenderingMode(.alwaysTemplate)
let imageView = UIImageView()
imageView.image = starImage
imageView.tintColor = UIColor.yellow // или .systemYellow для iOS 13+
imageView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
view.addSubview(imageView)
// 2. Альтернативный способ: установить tintColor на родительской вьюхе
let containerView = UIView()
containerView.tintColor = UIColor.red
containerView.addSubview(imageView) // изображение унаследует красный цвет
Важно: Изображение должно быть экспортировано как single-color silhouette (одноцветный силуэт). Если в вашей иконке есть градиенты или полупрозрачные области, Template режим может дать неожиданный результат. В Figma или Sketch экспортируйте иконку в формате PNG с прозрачным фоном, используя только белый или чёрный цвет для самого рисунка.
Решение для WatchKit (WKInterfaceImage)
В WatchKit процесс немного отличается. Вам нужно использовать метод setTintColor() у экземпляра WKInterfaceImage. Убедитесь, что изображение в вашем Watch-приложении также является Template-совместимым:
// В контроллере интерфейса WatchKit
@IBOutlet weak var starImage: WKInterfaceImage!
override func willActivate() {
super.willActivate()
// Устанавливаем изображение (оно должно быть в Assets Watch-приложения)
starImage.setImage(UIImage(named: "star"))
// Применяем цвет
starImage.setTintColor(UIColor.yellow)
}
Обратите внимание, что в WatchKit не нужно явно вызывать withRenderingMode(.alwaysTemplate) — система автоматически обрабатывает изображение как Template, если вы используете setTintColor. Но изображение всё равно должно быть силуэтом.
Дополнительные советы
- Используйте SF Symbols. Apple предоставляет бесплатный набор иконок SF Symbols, которые изначально поддерживают
tintColorи масштабирование. Например:UIImage(systemName: "star.fill"). - Проверьте альфа-канал. В Preview (или через код) убедитесь, что ваше изображение не содержит ненужных полупрозрачных пикселей. Для этого можно использовать
UIImage.alphaили визуальный осмотр в Xcode. - Анимируйте изменение цвета. Если нужно плавно менять цвет, используйте анимацию UIKit:
UIView.animate(withDuration: 0.3) { imageView.tintColor = newColor }.
Если после всех этих шагов проблема остаётся, скорее всего, исходное изображение некорректно подготовлено. Попробуйте перерисовать иконку в векторном редакторе, убедившись, что она состоит из одного сплошного цвета (обычно чёрного) на прозрачном фоне.
<<Альтернативный подход: Ручное перекрашивание с помощью Core Graphics
Если вы не хотите использовать Template режим (например, изображение содержит сложные градиенты или несколько цветов, которые нужно изменить частично), можно применить ручное перекрашивание через Core Graphics. Этот метод даёт полный контроль над пикселями.
extension UIImage {
func tinted(with color: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
guard let context = UIGraphicsGetCurrentContext(),
let cgImage = self.cgImage else { return nil }
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(.normal)
let rect = CGRect(origin: .zero, size: size)
context.clip(to: rect, mask: cgImage)
color.setFill()
context.fill(rect)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
// Использование:
let originalImage = UIImage(named: "star")
let yellowStar = originalImage?.tinted(with: .yellow)
imageView.image = yellowStar
Недостатки: Этот метод потребляет больше памяти и может быть медленнее на старых устройствах. Кроме того, он не работает в WatchKit напрямую (там нет Core Graphics), но вы можете подготовить изображение на iPhone и передать его на часы через WCSession.
Когда использовать: Если ваша иконка содержит полупрозрачные элементы, тени или сложные формы, которые Template режим искажает. Для простых силуэтов всё же лучше использовать стандартный tintColor.