Fix: UIImage tintColor Not Applying in UIKit and watchOS

Fix: UIImage tintColor Not Applying in UIKit and watchOS

Written by

in

Quick answer: Setting imageView.tintColor has no visible effect if the image uses its default rendering mode. Set the image rendering mode to .alwaysTemplate using image.withRenderingMode(.alwaysTemplate) or set ‘Render As’ to ‘Template Image’ in your Xcode Asset Catalog.

Why this happens

Setting imageView.tintColor = .systemRed often fails to change the color of an image in UIKit or watchOS. The image continues to display in its original colors, or appears as solid black if the source asset is a monochrome shape.

This occurs because UIImage instances default to .automatic rendering mode (UIImage.RenderingMode.automatic). When an image with automatic rendering is placed into a standard UIImageView, UIKit resolves the mode to .alwaysOriginal. In original mode, UIKit draws the raw bitmap pixel data and completely ignores the enclosing view’s tintColor property.

To allow tintColor to recolor an asset, UIKit needs instructions to treat the image as a mask (an alpha-only template) rather than a full-color bitmap. In watchOS using legacy WatchKit (WKInterfaceImage), dynamic code-level rendering mode conversion is not exposed at runtime on the image view, making asset catalog configuration mandatory.

How to fix it

1. Set .alwaysTemplate in Swift code

The most common programmatic fix in UIKit is to generate a new UIImage reference configured with .alwaysTemplate using withRenderingMode(_:).

import UIKit

final class IconViewController: UIViewController {
    private let iconImageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load the image and create a template version
        let originalImage = UIImage(named: "HeartIcon")
        let templateImage = originalImage?.withRenderingMode(.alwaysTemplate)

        // Assign to the image view and set the desired tint
        iconImageView.image = templateImage
        iconImageView.tintColor = .systemRed

        view.addSubview(iconImageView)
        iconImageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            iconImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            iconImageView.widthAnchor.constraint(equalToConstant: 44),
            iconImageView.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
}

When alwaysTemplate is active, UIKit discards all color information in the asset and fills every non-transparent pixel with the view’s current tintColor. If the view is placed inside a hierarchy with a inherited tintColor (such as a custom UIView or UIWindow), setting the image to template mode will automatically inherit that tint.

2. Configure “Render As: Template Image” in Asset Catalogs

Changing the rendering mode directly in your Xcode asset catalog is the cleanest approach. It eliminates the need for boilerplate withRenderingMode(.alwaysTemplate) calls across your codebase.

  1. In Xcode, open your project’s Assets.xcassets catalog.
  2. Select the image set that needs tinting.
  3. Open the Attributes Inspector in the right-hand utility panel (Option + Command + 5).
  4. Locate the Render As pop-up menu.
  5. Change the value from Default to Template Image.

Once configured, every instance of UIImage(named: "YourAsset") automatically loads with template rendering enabled. You can assign it directly to UIImageView.image and immediately alter tintColor without extra code.

3. Tint template images in watchOS (WatchKit)

In watchOS apps using WatchKit interface controllers, WKInterfaceImage does not support creating dynamic template copies via code. You must configure the rendering mode in the asset catalog first.

  1. Add the asset to the WatchKit App’s Asset Catalog.
  2. Set Render As to Template Image in the Attributes Inspector.
  3. Add a WKInterfaceImage to your storyboard or interface controller.
  4. Apply the tint programmatically using setTintColor(_:):
import WatchKit

class StatusInterfaceController: WKInterfaceController {
    @IBOutlet weak var statusImage: WKInterfaceImage!

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)

        // Set the image by name from the asset catalog
        statusImage.setImageNamed("HeartIcon")

        // Apply the dynamic tint color
        statusImage.setTintColor(.red)
    }
}

If you omit setTintColor(_:), watchOS applies the Global Tint specified in your WatchKit app target settings. If no Global Tint is set, watchOS defaults to light blue.

4. Recolor images in modern SwiftUI

If you are building your view in SwiftUI, the same principle applies. Bitmap images default to original rendering unless you chain the .renderingMode(_:) modifier before applying color.

import SwiftUI

struct TintedIconView: View {
    var body: some View {
        VStack(spacing: 16) {
            // Custom asset requiring template mode
            Image("HeartIcon")
                .renderingMode(.template)
                .foregroundStyle(.red)

            // SF Symbols use template rendering by default
            Image(systemName: "heart.fill")
                .foregroundStyle(.blue)
        }
    }
}

SF Symbols created via Image(systemName:) are vector symbols configured as templates automatically. Custom PNG or SVG files added to asset catalogs must have either the .renderingMode(.template) modifier applied or the Asset Catalog “Render As” set to Template Image.

How to prevent it

  • Mark monochrome icons, glyphs, and navigation shapes as Template Image in Assets.xcassets immediately upon importing them.
  • Cache template images instead of calling withRenderingMode(.alwaysTemplate) repeatedly inside fast-scrolling UICollectionViewCell or UITableViewCell reuse methods.
  • Prefer Apple’s built-in SF Symbols for standard iconography, as they automatically handle vector scaling, dynamic type, and template rendering without manual configuration.
  • Test views in both Light and Dark modes to ensure the assigned tintColor provides sufficient contrast against the background.

FAQ

Why does tintColor work on SF Symbols without calling withRenderingMode?

SF Symbols are designed as vector glyphs rather than traditional raster bitmaps. UIKit and SwiftUI automatically configure them with template or hierarchical rendering modes by default, allowing them to adapt directly to the view hierarchy’s tint or foreground style.

Can I tint only part of a multi-color image using template mode?

No. Template mode treats all opaque and semi-opaque pixels identically, recoloring the entire shape using a single solid tint based on alpha values. To color individual layers independently, use multi-color SF Symbols, layered vector assets, or multi-palette SVGs configured in Xcode.

How do I revert an image back to its original bitmap colors?

Call image.withRenderingMode(.alwaysOriginal) on your UIImage instance, or set the Render As property in the Xcode Asset Catalog to Original Image. This forces UIKit to bypass any inherited or explicitly assigned tintColor.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.