¿Cómo puedo cambiar la imagen tintColor en iOS y WatchKit?

395

Tengo un UIImageView llamado "theImageView", con UIImage en un solo color (fondo transparente) al igual que el corazón negro izquierdo debajo. ¿Cómo puedo cambiar el color de tinte de esta imagen mediante programación en iOS 7 o superior, según el método de tinte utilizado en los iconos de la barra de navegación de iOS 7+?

¿Puede este método funcionar también en WatchKit para una aplicación Apple Watch?

ingrese la descripción de la imagen aquí

correoso
fuente
44
¿Qué quiere decir con "el siguiente código es incorrecto"? Establecer un UIImagecon UIImageRenderingModeAlwaysTemplatey luego establecer UIImageVIewel tintColorSI funciona. (en mi código ^^)
Vinzzz
2
Use un png con transparencia como esta
Alladinian
44
Realmente deberías mover tu respuesta a la sección de respuestas, ya que creo que es la mejor y más moderna.
Richard Venable
¡Ojalá pudiera hacer una doble votación!
Jacobo Koenig

Respuestas:

764

iOS
Para una aplicación iOS, en Swift 3, 4 o 5:

theImageView.image = theImageView.image?.withRenderingMode(.alwaysTemplate)
theImageView.tintColor = UIColor.red

Para Swift 2:

theImageView.image = theImageView.image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
theImageView.tintColor = UIColor.redColor()

Mientras tanto, la solución moderna de Objective-C es:

theImageView.image = [theImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor redColor]];

Watchkit
En WatchKit para aplicaciones de Apple Watch, puede establecer el color de tinte para una imagen de plantilla .

  1. Debe agregar su imagen a un Catálogo de activos en su aplicación WatchKit y configurar el conjunto de imágenes para que se represente como una Imagen de plantilla en el Inspector de atributos. A diferencia de una aplicación para iPhone, actualmente no puede configurar la representación de la plantilla en el código de la extensión WatchKit.
  2. Configure esa imagen para usarla en su WKInterfaceImage en el generador de interfaces para su aplicación
  3. Cree un IBOutlet en su WKInterfaceController para la WKInterfaceImage llamada 'theImage' ...

Para configurar el color de tinte en Swift 3 o 4:

theImage.setTintColor(UIColor.red)

Swift 2:

theImage.setTintColor(UIColor.redColor())

Para configurar el color de tinte en Objective-C:

[self.theImage setTintColor:[UIColor redColor]];

Si usa una imagen de plantilla y no aplica un color de tinte, se aplicará el Tinte global para su aplicación WatchKit. Si no ha configurado un Tinte global, theImagese teñirá de azul claro de forma predeterminada cuando se use como imagen de plantilla.

Duncan Babbage
fuente
2
Esta es la mejor y más simple solución.
Ankish Jain
44
imageWithRenderingMode es demasiado lento. En storyboard e imagen Activos. también puede cambiar estos dos: actualice el modo de renderizado a la imagen de la plantilla - esa es una mejor solución
Katerina
Perfecto, ahora uso este método basado en su código: + (UIImageView ) tintImageView: (UIImageView *) imageView withColor: (UIColor ) color {imageView.image = [imageView.image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate]; [imageView setTintColor: color]; volver imageView; }
Josep Escobar
¿Es mejor con la imagen en negro?
Bruno
1
La imagen de @Bruno no necesita ser negra, no. Funciona con cualquier color.
Duncan Babbage
121

Aquí hay una categoría que debería hacer el truco

@interface UIImage(Overlay)
@end

@implementation UIImage(Overlay)

- (UIImage *)imageWithColor:(UIColor *)color1
{
        UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(context, 0, self.size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextSetBlendMode(context, kCGBlendModeNormal);
        CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
        CGContextClipToMask(context, rect, self.CGImage);
        [color1 setFill];
        CGContextFillRect(context, rect);
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return newImage;
}
@end

entonces harías:

theImageView.image = [theImageView.image imageWithColor:[UIColor redColor]];
estoy impresionado
fuente
Gracias por esta respuesta muy válida, supongo que mi código estaba bien desde el principio, debo responder a mi propia pregunta y le dio un 1 sin tener en cuenta ..
masticable
104

Tuve que hacer esto en Swift usando un extension.

Pensé en compartir cómo lo hice:

extension UIImage {
    func imageWithColor(color1: UIColor) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        color1.setFill()

        let context = UIGraphicsGetCurrentContext() as CGContextRef
        CGContextTranslateCTM(context, 0, self.size.height)
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextSetBlendMode(context, CGBlendMode.Normal)

        let rect = CGRectMake(0, 0, self.size.width, self.size.height) as CGRect
        CGContextClipToMask(context, rect, self.CGImage)
        CGContextFillRect(context, rect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext() as UIImage
        UIGraphicsEndImageContext()

        return newImage
    }
}

Uso:

theImageView.image = theImageView.image.imageWithColor(UIColor.redColor())

Swift 4

extension UIImage {
    func imageWithColor(color1: UIColor) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        color1.setFill()

        let context = UIGraphicsGetCurrentContext()
        context?.translateBy(x: 0, y: self.size.height)
        context?.scaleBy(x: 1.0, y: -1.0)
        context?.setBlendMode(CGBlendMode.normal)

        let rect = CGRect(origin: .zero, size: CGSize(width: self.size.width, height: self.size.height))
        context?.clip(to: rect, mask: self.cgImage!)
        context?.fill(rect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage!
    }
}

Uso:

theImageView.image = theImageView.image?.imageWithColor(color1: UIColor.red)

fulvio
fuente
1
FYI esto no funcionó para mí hasta que me moví a la color1.setFill()derecha debajo de la primera línea del método UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale).
Aaron
@Aaron actualizado en función de tu comentario. Gracias.
fulvio
77
@CeceXX uso CGBlendMode.Normalen su lugar
Adolfo
2
Eres increíble, sin embargo, es posible que tengas que cambiarlo a Swift 3
SimpuMind
1
@SimpiMind Proporcionó Swift 4 en su lugar.
fulvio
97

En storyboard e imagen Activos. puedes cambiar estos dos también:

Actualice el modo de renderizado a la imagen de plantilla

Actualice el modo de representación a la imagen de plantilla en los activos de imagen

Actualice el Color de tinte en Vistas.

Actualice el Color de tinte en Vistas en Vistas

JerryZhou
fuente
22
¡Esta es seriamente la respuesta más rudo!
brandonscript
1
Establecer este valor desde el guión gráfico nunca funciona para mí. Siempre tengo que usar el imageView.tintColorcódigo.
Kamil Powałowski
3
@ KamilPowałowski para mí esto funciona a veces ... No estoy seguro de por qué. Desearía saber por qué no siempre funciona. Así que termino haciéndolo por código
Jesús Rodríguez, el
2
Para mí, este método de guión gráfico funciona en botones pero no en imágenes vistas. Todavía tengo que configurar tintColor en el código para imageViews.
Derek Soike
2
En caso de que alguien todavía se rasque la cabeza preguntándose por qué no está funcionando en IB, intente configurar el opaco de imageView en No.
Bonan
40

Swift 4

Cambie el tinte de UIImage SVG / PDF , que funciona para imágenes con colores únicos :

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

import Foundation

// MARK: - UIImage extensions

public extension UIImage {

    //
    /// Tint Image
    ///
    /// - Parameter fillColor: UIColor
    /// - Returns: Image with tint color
    func tint(with fillColor: UIColor) -> UIImage? {
        let image = withRenderingMode(.alwaysTemplate)
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        fillColor.set()
        image.draw(in: CGRect(origin: .zero, size: size))

        guard let imageColored = UIGraphicsGetImageFromCurrentImageContext() else {
            return nil
        }

        UIGraphicsEndImageContext()
        return imageColored
    }
}

Cambie el tinte de UIImageView , que funciona para imágenes con colores únicos :

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

let imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
imageView.image = UIImage(named: "hello.png")!.withRenderingMode(.alwaysTemplate)
imageView.tintColor = .yellow

Cambie el tinte de UIImage por imagen , use eso:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

import Foundation

// MARK: - Extensions UIImage

public extension UIImage {

    /// Tint, Colorize image with given tint color
    /// This is similar to Photoshop's "Color" layer blend mode
    /// This is perfect for non-greyscale source images, and images that 
    /// have both highlights and shadows that should be preserved<br><br>
    /// white will stay white and black will stay black as the lightness of 
    /// the image is preserved
    ///
    /// - Parameter TintColor: Tint color
    /// - Returns:  Tinted image
    public func tintImage(with fillColor: UIColor) -> UIImage {

        return modifiedImage { context, rect in
            // draw black background - workaround to preserve color of partially transparent pixels
            context.setBlendMode(.normal)
            UIColor.black.setFill()
            context.fill(rect)

            // draw original image
            context.setBlendMode(.normal)
            context.draw(cgImage!, in: rect)

            // tint image (loosing alpha) - the luminosity of the original image is preserved
            context.setBlendMode(.color)
            fillColor.setFill()
            context.fill(rect)

            // mask by alpha values of original image
            context.setBlendMode(.destinationIn)
            context.draw(context.makeImage()!, in: rect)
        }
    }

    /// Modified Image Context, apply modification on image
    ///
    /// - Parameter draw: (CGContext, CGRect) -> ())
    /// - Returns:        UIImage
    fileprivate func modifiedImage(_ draw: (CGContext, CGRect) -> ()) -> UIImage {

        // using scale correctly preserves retina images
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        let context: CGContext! = UIGraphicsGetCurrentContext()
        assert(context != nil)

        // correctly rotate image
        context.translateBy(x: 0, y: size.height)
        context.scaleBy(x: 1.0, y: -1.0)

        let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)

        draw(context, rect)

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }
}
YannSteph
fuente
1
Hola, soy nuevo en Swift, pero me dijiste aquí que es para imágenes SVG, pero no puedo encontrar una manera de analizar el SVG a UIImage, ¿podrías ayudarme? o tal vez de alguna manera puedo manejar esto con el SVG correctamente. ¡Gracias!
Dumitru Rogojinaru
@DumitruRogojinaru usa la función SVG con la imagen de la plantilla en el activo
YannSteph
¿Por qué es necesario traducir y escalar en "func modifiedImage"?
Luca Davanzo
Actualización para Swift 4
YannSteph
36

Si a alguien le importa una solución sin UIImageView:

// (Swift 3)
extension UIImage {
    func tint(with color: UIColor) -> UIImage {
        var image = withRenderingMode(.alwaysTemplate)
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        color.set()

        image.draw(in: CGRect(origin: .zero, size: size))
        image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }
}
Puttin
fuente
Wow, funciona como magia después de buscar esto por más de una hora. Necesario para: Establecer un ícono en un NSTextAttachment en un color diferente al original del ícono. La respuesta estándar para usar un UIImageView y cambiar su tintColor no funciona aquí, porque NSTextAttachment no toma el UIImageView.
marco
1
Esta es la mejor solución que he encontrado hasta ahora, especialmente para cualquiera que busque un código que funcione con Swift 3. ¡Gran sugerencia!
shashwat
17

Con Swift

let commentImageView = UIImageView(frame: CGRectMake(100, 100, 100, 100))
commentImageView.image = UIImage(named: "myimage.png")!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
commentImageView.tintColor = UIColor.blackColor()
addSubview(commentImageView)
Esqarrouth
fuente
3
simplemente puedes poner .AlwaysTemplate.
Rui Peres
Sí, acorta el código, pero parece que podría reducir la claridad del código. no estoy seguro acerca de los atajos de punto debido a eso
Esqarrouth
Veo tu POV, solo una alternativa.
Rui Peres
4

Prueba esto

http://robots.thoughtbot.com/designing-for-ios-blending-modes

o

- (void)viewDidLoad
{
[super viewDidLoad];

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 30, 300, 50)];
label.numberOfLines = 0;
label.font = [UIFont systemFontOfSize:13];
label.text = @"These checkmarks use the same gray checkmark image with a tintColor applied to the image view";
[self.view addSubview:label];

[self _createImageViewAtY:100 color:[UIColor purpleColor]];
}

- (void)_createImageViewAtY:(int)y color:(UIColor *)color {
UIImage *image = [[UIImage imageNamed:@"gray checkmark.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CGRect frame = imageView.frame;
frame.origin.x = 100;
frame.origin.y = y;
imageView.frame = frame;

if (color)
    imageView.tintColor = color;

[self.view addSubview:imageView];
}
yazh
fuente
4

Para fines rápidos 3

theImageView.image = theImageView.image!.withRenderingMode(.alwaysTemplate) theImageView.tintColor = UIColor.red

DairySeeker
fuente
2

Para teñir la imagen de un UIButton

let image1 = "ic_shopping_cart_empty"
btn_Basket.setImage(UIImage(named: image1)?.withRenderingMode(.alwaysTemplate), for: .normal)
btn_Basket.setImage(UIImage(named: image1)?.withRenderingMode(.alwaysTemplate), for: .selected)
btn_Basket.imageView?.tintColor = UIColor(UIColor.Red)
Firas Shrourou
fuente
2

iOS

Solución para hacerlo desde Interface Builder, establezca templateImage param en keyPath y elija su color de tinte de IB

extension UIImageView {

// make template image with tint color
var templateImage: Bool {
    set {
        if newValue, let image = self.image {
            let newImage = image.withRenderingMode(.alwaysTemplate)
            self.image = newImage
        }
    } get {
        return false
    }
}

}

Hakob
fuente
2

Con iOS 13 y superior, simplemente puede usar

let image = UIImage(named: "Heart")?.withRenderingMode(.alwaysTemplate)
if #available(iOS 13.0, *) {
   imageView.image = image?.withTintColor(UIColor.white)
}
KingofBliss
fuente
1

Aproveche la extensión en Swift: -

extension UIImageView {
    func changeImageColor( color:UIColor) -> UIImage
    {
        image = image!.withRenderingMode(.alwaysTemplate)
        tintColor = color
        return image!
    }
}

   //Change color of logo 
   logoImage.image =  logoImage.changeImageColor(color: .red)

ingrese la descripción de la imagen aquí

Shrawan
fuente
1

Swift 3 versión de extensión de respuesta de fuzz

func imageWithColor(color: UIColor) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
    color.setFill()

    let context = UIGraphicsGetCurrentContext()! as CGContext
    context.translateBy(x: 0, y: self.size.height)
    context.scaleBy(x: 1.0, y: -1.0);
    context.setBlendMode(.normal)

    let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) as CGRect
    context.clip(to: rect, mask: self.cgImage!)
    context.fill(rect)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()! as UIImage
    UIGraphicsEndImageContext()

    return newImage
}
Bawpotter
fuente
0

Ahora uso este método basado en la respuesta de Duncan Babbage:

+ (UIImageView *) tintImageView: (UIImageView *)imageView withColor: (UIColor*) color{
    imageView.image = [imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    [imageView setTintColor:color];
    return imageView;
}
Josep Escobar
fuente
0

Puede usar esto en Swift 3 si tiene una imagen para reemplazar el botón de borrar

func addTextfieldRightView(){

    let rightViewWidth:CGFloat = 30

    let viewMax = self.searchTxt.frame.height
    let buttonMax = self.searchTxt.frame.height - 16

    let buttonView = UIView(frame: CGRect(
        x: self.searchTxt.frame.width - rightViewWidth,
        y: 0,
        width: viewMax,
        height: viewMax))

    let myButton = UIButton(frame: CGRect(
        x: (viewMax - buttonMax) / 2,
        y: (viewMax - buttonMax) / 2,
        width: buttonMax,
        height: buttonMax))

    myButton.setImage(UIImage(named: "BlueClear")!, for: .normal)

    buttonView.addSubview(myButton)

    let clearPressed = UITapGestureRecognizer(target: self, action: #selector(SearchVC.clearPressed(sender:)))
    buttonView.isUserInteractionEnabled = true
    buttonView.addGestureRecognizer(clearPressed)

    myButton.addTarget(self, action: #selector(SearchVC.clearPressed(sender:)), for: .touchUpInside)

    self.searchTxt.rightView = buttonView
    self.searchTxt.rightViewMode = .whileEditing
}
Chester Schofield
fuente
0

Subclase que también se puede usar desde el generador de código e interfaz:

@implementation TintedImageView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self setup];
    }
    return self;
}

-(void)setup {
    self.image = [self.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}

@end
Łukasz Gierałtowski
fuente
0

Esta es mi extensión UIImage y puede usar directamente la función changeTintColor para una imagen.

extension UIImage {

    func changeTintColor(color: UIColor) -> UIImage {
        var newImage = self.withRenderingMode(.alwaysTemplate)
        UIGraphicsBeginImageContextWithOptions(self.size, false, newImage.scale)
        color.set()
        newImage.draw(in: CGRect(x: 0.0, y: 0.0, width: self.size.width, height: self.size.height))
        newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }

    func changeColor(color: UIColor) -> UIImage {
        let backgroundSize = self.size
        UIGraphicsBeginImageContext(backgroundSize)
        guard let context = UIGraphicsGetCurrentContext() else {
            return self
        }
        var backgroundRect = CGRect()
        backgroundRect.size = backgroundSize
        backgroundRect.origin.x = 0
        backgroundRect.origin.y = 0

        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        context.setFillColor(red: red, green: green, blue: blue, alpha: alpha)
        context.translateBy(x: 0, y: backgroundSize.height)
        context.scaleBy(x: 1.0, y: -1.0)
        context.clip(to: CGRect(x: 0.0, y: 0.0, width: self.size.width, height: self.size.height),
                 mask: self.cgImage!)
        context.fill(backgroundRect)

        var imageRect = CGRect()
        imageRect.size = self.size
        imageRect.origin.x = (backgroundSize.width - self.size.width) / 2
        imageRect.origin.y = (backgroundSize.height - self.size.height) / 2

        context.setBlendMode(.multiply)
        context.draw(self.cgImage!, in: imageRect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }

}

Ejemplo de uso como este

let image = UIImage(named: "sample_image")
imageView.image = image.changeTintColor(color: UIColor.red)

Y puede usar la changeColorfunción de cambio para cambiar el color de la imagen

abdullahselek
fuente
0

profileImageView.image = theImageView.image! .withRenderingMode (.alwaysTemplate)
profileImageView.tintColor = UIColor.green

O

Primero seleccione Imagen particular en el recurso de imagen y luego seleccione enrojecido como Plantilla en lugar de Predeterminado y después de esa línea de escritura. profileImageView.tintColor = UIColor.green

Priyanka Jadhav
fuente
0

Si tiene alguna identificación para la imagen SVG, puede rellenar los colores con respecto a la identificación.

    let image = SVGKImage(named: "iconName")
    let svgIMGV = SVGKFastImageView(frame: self.imgView.frame)
         svgIMGV.image = image
          svgIMGV.fillTintColor(colorImage: UIColor.red, iconID: "Bank")
// Add in extension SVGKImageView
extension SVGKImageView {
 func fillTintColor(colorImage: UIColor, iconID: String) {
        if self.image != nil && self.image.caLayerTree != nil {
            print(self.image.caLayerTree.sublayers)
            guard let sublayers = self.image.caLayerTree.sublayers else { return }
            fillRecursively(sublayers: sublayers, color: colorImage, iconID: iconID)
        }
    }

     private func fillRecursively(sublayers: [CALayer], color: UIColor, iconID: String, hasFoundLayer: Bool) {
        var isLayerFound = false
        for layer in sublayers {
            if let l = layer as? CAShapeLayer {

                print(l.name)                
                //IF you want to color the specific shapelayer by id else remove the l.name  == "myID"  validation
                if let name =  l.name,  hasFoundLayer == true && name == "myID" {
                    self.colorThatImageWIthColor(color: color, layer: l)
                    print("Colouring FInished")
                }
            } else {
                if layer.name == iconID {
                    if let innerSublayer = layer.sublayers as? [CAShapeLayer] {
                        fillRecursively(sublayers: innerSublayer, color: color, iconID: iconID, hasFoundLayer: true )
                        print("FOund")
                    }
                } else {
                    if let l = layer as? CALayer, let sub = l.sublayers {
                        fillRecursively(sublayers: sub, color: color, iconID: iconID, hasFoundLayer: false)
                    }
                }
            }

        }
    }

    func colorThatImageWIthColor(color: UIColor, layer: CAShapeLayer) {
        if layer.strokeColor != nil {
            layer.strokeColor = color.cgColor
        }
        if layer.fillColor != nil {
            layer.fillColor = color.cgColor
        }
    }

}

O Mira este ejemplo.

https://github.com/ravisfortune/SVGDEMO

K Ravi Kumar
fuente
0
let navHeight = self.navigationController?.navigationBar.frame.height;
let menuBtn = UIButton(type: .custom)
menuBtn.frame = CGRect(x: 0, y: 0, width: 45, height: navHeight!)     
menuBtn.setImage(UIImage(named:"image_name")!.withRenderingMode(.alwaysTemplate), for: .normal)        
menuBtn.tintColor = .black
delabahan
fuente