Image quality loss when reassigning image to UIImageView - ios

I am developing an application which can tint colors in an image and export it.
I have added 3 versions of an image asset to my Assets.xcassets folder.
These are 3 different sizes of the same image, namely:
image1.png image1#2x.png image1#3x.png
with respective sizes: 256x341, 512x683, 768x1024 pixels.
I have created a UIImageView on my storyboard called myImage and assigned image1 to myImage via storyboard->utilities->attributes inspector-> Image View -> Image.
I am trying to use the following tintWithColor function as a UIImage extension to change the color of this image.
extension UIImage {
func tintWithColor(color:UIColor)->UIImage {
UIGraphicsBeginImageContext(self.size)
let context = UIGraphicsGetCurrentContext()
self.drawAtPoint(CGPoint(x: 0, y: 0))
CGContextSaveGState(context)
// flip the image
CGContextScaleCTM(context, 1.0, -1.0)
CGContextTranslateCTM(context, 0.0, -self.size.height)
// multiply blend mode
CGContextSetBlendMode(context, CGBlendMode.Multiply)
let rect = CGRectMake(0, 0, self.size.width, self.size.height)
CGContextClipToMask(context, rect, self.CGImage)
color.setFill()
CGContextFillRect(context, rect)
CGContextRestoreGState(context)
// create uiimage
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
As I am testing this function on my viewDidLoad() to change the color of myImage.image as below, I see that (on whatever device I am using) the width and height of my originalImage is always 256 x 341,5
override func viewDidLoad() {
super.viewDidLoad()
let originalImage = myImage.image
print("LOG: original image width: \(originalImage!.size.width) height: \(originalImage!.size.height)")
let tintedImage = originalImage!.tintWithColor(UIColor(hue: 300/360, saturation: 0.70, brightness: 0.70, alpha: 0.6))
myImage.image = tintedImage
// Do any additional setup after loading the view.
}
If I don't apply these changes on my image, the quality of my image on an iPhone 6 (4,7") device looks as below:
If I apply the tintWithColor and then reassign the resulting image to my imageView the quality seems to drop, lines smoothen, as can be seen below:
Is there a way to avoid this quality loss?
Eventually I will be exporting the high quality image, so I would like to apply this color tint function on the high quality version of this image.
Thank you.

UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.size.width, self.size.height), false, 3.0)
should work

Related

Swift - How to change a png tint color without losing the clear background? [duplicate]

I am new to swift and trying to achieve this essentially,
This image to
This image ->
I am using this code from here to change the tint on image but do not get the desired output
func tint(image: UIImage, color: UIColor) -> UIImage
{
let ciImage = CIImage(image: image)
let filter = CIFilter(name: "CIMultiplyCompositing")
let colorFilter = CIFilter(name: "CIConstantColorGenerator")
let ciColor = CIColor(color: color)
colorFilter.setValue(ciColor, forKey: kCIInputColorKey)
let colorImage = colorFilter.outputImage
filter.setValue(colorImage, forKey: kCIInputImageKey)
filter.setValue(ciImage, forKey: kCIInputBackgroundImageKey)
return UIImage(CIImage: filter.outputImage)!
}
If this is a noob question, apologies. I was able to do this easily in javascript but not in swift.
hi you can use it the following way. First the UIImage Extension that you used needs some updates.
you can copy the code below for Swift 3
extension UIImage{
func tint(color: UIColor, blendMode: CGBlendMode) -> UIImage
{
let drawRect = CGRect(x: 0,y: 0,width: size.width,height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, scale)
color.setFill()
UIRectFill(drawRect)
draw(in: drawRect, blendMode: blendMode, alpha: 1.0)
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return tintedImage!
}
}
Then in your viewDidLoad where you are using the image
for example i used the image from IBOutlet imageWood
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.imageWood.image = self.imageWood.image?.tint(color: UIColor.green, blendMode: .saturation)
}
You will have to use the appropriate color and the images
The other Extension i found
extension UIImage {
// 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
// white will stay white and black will stay black as the lightness of the image is preserved
func tint(_ tintColor: 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(self.cgImage!, in: rect)
// tint image (loosing alpha) - the luminosity of the original image is preserved
context.setBlendMode(.color)
tintColor.setFill()
context.fill(rect)
// mask by alpha values of original image
context.setBlendMode(.destinationIn)
context.draw(self.cgImage!, in: rect)
}
}
// fills the alpha channel of the source image with the given color
// any color information except to the alpha channel will be ignored
func fillAlpha(_ fillColor: UIColor) -> UIImage {
return modifiedImage { context, rect in
// draw tint color
context.setBlendMode(.normal)
fillColor.setFill()
context.fill(rect)
// mask by alpha values of original image
context.setBlendMode(.destinationIn)
context.draw(self.cgImage!, in: rect)
}
}
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!
}
}
Using it like this
self.imageWood.image = self.imageWood.image?.tint(UIColor.purple.withAlphaComponent(1))
Try the below code, should work for you.
if let myImage = UIImage(named: "imageName")?.withRenderingMode(.alwaysTemplate) {
myImageView.image = myImage
myImageView.tintColor = UIColor.white
}
Your image is simple and only has one color. I would suggest making your image transparent except where the lines are, and then layer it on top of a white background to get your results.
It looks like you got the results you're after using drawing modes. There are also a number of Core image filters that let you apply tinting effects to images, as well as replacing specific colors. Those would be a good choice for more complex images.

ios UIImageRenderingModeAlwaysTemplate doesn't ignore opacity

I am trying to dynamically change the color and opacity of the images given to me by the designer. Of course, it works seamlessly with the following code :
_imgViewForMenu.tintColor = [_lblForMenu.textColor colorWithAlphaComponent:1.0f];
// This alpha component wont affect the png image with 38% opacity.
// You will never get full black image with [UIColor blackColor]
// and alpha component 1.0
_imgViewForMenu.image = [imageForMenuIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
This will work, but only when the image has no opacity of its own. Else as said in the comment for the code, it wont work.
So the question is, how do you render an image to ignore both its color component as well as opacity. The system controls like UITabBar and UIBarButonItem seem to do it with ease. Why not with UIImageView then?
Try this:
extension UIImage {
func tinted(with color: UIColor) -> UIImage? {
let image = withRenderingMode(.alwaysTemplate)
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
color.set()
image.draw(in: CGRect(x: 0.0, y: 0.0, width: image.size.width, height: image.size.height))
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return tintedImage
}
}

I have a png file with no background, how can I create a clear color background for this image in iOS?

So I understand that a UIImage inherently doesn't have a background. As many of us know, a lot of PNG files don't have a background Color thus making it clear. I'm attempting to upload a png file that doesn't have a background color, thus a clear color. Yes, I know I can set the background Color myself in adobe or sketch, but I'm assuming that other users don't know how to do this.
Here is a screenshot of the png that I have created:
As you can see, it's just two lines that are unioned together so there's no background set.
Now below is a screenshot of the aftermath of using the imagePicker to choose this png image from my photo roll.
Notice that the area that is supposed to be transparent is actually black. I want to color in the black part and make it actually clearColor instead and keep the green cross as it is. Now, I'm not sure if the black color is actually even a black color because perhaps it's just empty space. Can I fill in the empty black space and turn it into a clear color?
Here's my code right now that isn't working very well:
func overlayImage(image: UIImage, color: UIColor) -> UIImage? {
let rect = CGRectMake(0, 0, image.size.width, image.size.height)
let backgroundView = UIView(frame: rect)
backgroundView.backgroundColor = color
UIGraphicsBeginImageContext(rect.size)
let gcSize: CGSize = backgroundView.frame.size
UIGraphicsBeginImageContext(gcSize)
let context: CGContextRef = UIGraphicsGetCurrentContext()!
backgroundView.layer.renderInContext(context)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Any help in either obj-C or Swift would be greatly appreciated.
I got rid of the overlay method above and am using the code below:
Updated with new code that still doesn't work
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
scaleImage(overlayImage(image, color: UIColor.clearColor()))
}
func scaleImageAndAddAugmented(image: UIImage?) {
let rect = CGRectMake(0, 0, image!.size.width, image!.size.height)
let backgroundView = UIView(frame: rect)
backgroundView.backgroundColor = UIColor.clearColor()
let size = CGSizeApplyAffineTransform(image!.size, CGAffineTransformMakeScale(0.25, 0.25))
let hasAlpha = false
let scale: CGFloat = 0.0
UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
image!.drawInRect(CGRect(origin: CGPointZero, size: size))
let context = CGBitmapContextCreate(nil, Int(image!.size.width), Int(image!.size.height), 8, 0, CGColorSpaceCreateDeviceRGB(), CGImageAlphaInfo.PremultipliedLast.rawValue)
let myGeneratedImage = CGBitmapContextCreateImage(context)
CGContextDrawImage(context, rect, myGeneratedImage)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.dismissViewControllerAnimated(true, completion: nil)
// set image below
}
It seems a bit of excess work to use UIGraphicsGetImageFromCurrentImageContext to generate an image from context, when you can just create an image from a file.
As first mentioned it is required to have Opaque = NO:
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
My bet is its the image itself, because the whole thing is faded.
Make 100% certain that the passed colour is a clear colour for:
backgroundView.backgroundColor = color
You could create a bitmap context and use that instead:
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
8,
0,
rgbColorSpace,
kCGImageAlphaPremultipliedLast);
Rather than using the current context and you can use:
myGeneratedImage = CGBitmapContextCreateImage(context)
Drawing is easy as pie:
CGContextDrawImage(realContext,bounds,myGeneratedImage)
Swift code below:
func scaleImage(image: UIImage?) {
if let image = image {
let rect = CGRectMake(0, 0, image.size.width, image.size.height)
let size: CGSize = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.25, 0.25))
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
image.drawInRect(CGRect(origin: CGPointZero, size: size))
let context = CGBitmapContextCreate(nil, Int(image.size.width), Int(image.size.height), 8, 0, CGColorSpaceCreateDeviceRGB(), CGImageAlphaInfo.PremultipliedLast.rawValue)
let myGeneratedImage = CGBitmapContextCreateImage(context)
CGContextDrawImage(context, rect, myGeneratedImage)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}

Rendering image with textview

I am placing a textview on a image. With following code:
var previewImageView=UIImageView()
self.previewImageView.frame = CGRect(x:0, y:0, width:UIScreen.mainScreen().bounds.width, height:UIScreen.mainScreen().bounds.height)
self.view.addSubview(self.previewImageView)
textField = UITextView(frame: CGRect(x: 0, y: self.view.bounds.height/2 - 50, width: self.view.bounds.width, height: 36))
textField.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
self.view.addSubview(textField)
It works well. But I want to render this image with textview. Then I will save it to gallery.
How can I do this?
Edit:
I tried following code but it is only rendering image not textview:
//Setup the image context using the passed image.
UIGraphicsBeginImageContext(inImage.size)
//Put the image into a rectangle as large as the original image.
inImage.drawInRect(CGRectMake(0, 0, inImage.size.width, inImage.size.height))
// Creating a point within the space that is as bit as the image.
var rect: CGRect = CGRectMake(atPoint.x, atPoint.y, inImage.size.width, inImage.size.height)
//Now Draw the text into an image.
drawText.drawRect(rect)
// Create a new image out of the images we have created
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
// End the context now that we have the image we need
UIGraphicsEndImageContext()
//And pass it back up to the caller.
return newImage
You can render whole view into an UIImage using CGGraphicsContext. Here is an example of the code:
func imageWithView(view: UIView!) -> UIImage! {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
view.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
When you pass scale = 0.0 it will take a scale of your screen.
An example of usage:
let image = imageWithView(view)
Code to save image:
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
More information about UIGraphicsContext and image saving can be found here.
Update:
If you want to render only specific views, then you should use CALayer, renderInContext. When you use renderInContext it does not know views.frame and draws at (0:0) in coordinate system, so you should use CGContextTranslateCTM to move coordinate system.
To render only these two views you can do:
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, previewImageView.frame.origin.x, previewImageView.frame.origin.y)
previewImageView.layer.renderInContext(context)
CGContextTranslateCTM(context, textField.frame.origin.x - previewImageView.frame.origin.x, textField.frame.origin.y - previewImageView.frame.origin.y)
textField.layer.renderInContext(context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Please set opaque true/false as you need. When I was testing in playground I was using not opaque views.
Update 2:
Code which should work in all scenarios:
func imageFromViews(views: [UIView]!, contextSize: CGSize!) -> UIImage {
UIGraphicsBeginImageContextWithOptions(contextSize, false, 0.0)
let context = UIGraphicsGetCurrentContext()
for view in views {
CGContextTranslateCTM(context, view.frame.origin.x, view.frame.origin.y)
view.layer.renderInContext(UIGraphicsGetCurrentContext())
CGContextTranslateCTM(context, -view.frame.origin.x, -view.frame.origin.y)
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}

Change color of png in buttons - ios

I've got a set of icons that I've created that are transparent white PNGs:
And what I'd like to do is be able to tint them to other colors. Such as blue, grey, etc.
I've noticed that 'clicked/tapped' they change automatically to a grey. So I assume I can change that grey to whatever I like either with a tap or its normal state:
What would be the best way to achieve this?
Following code will set tint colour for normal state of button:
For Swift 4 and newer:
let origImage = UIImage(named: "imageName")
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
btn.setImage(tintedImage, for: .normal)
btn.tintColor = .red
You can change tint colour according to your need when state changes for button.
Older versions
For Swift 3:
let origImage = UIImage(named: "imageName")
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
btn.setImage(tintedImage, forState: .normal)
btn.tintColor = .redColor
For Swift 2:
see revision history.
I found the easiest approach below,
Open assetcatalog and select the image then go to attributes inspector and change Render As to Template Image as below
Then add below code in button Action method
yourButton.tintColor = .gray
Swift 4 or 5
extension UIButton{
func setImageTintColor(_ color: UIColor) {
let tintedImage = self.imageView?.image?.withRenderingMode(.alwaysTemplate)
self.setImage(tintedImage, for: .normal)
self.tintColor = color
}
}
Use:
button.setImage(UIImage(named: "image_name"), for: .normal) // You can set image direct from Storyboard
button.setImageTintColor(UIColor.white)
iOS 7 introduced a property called tintColor for views (including UIImageView). However you also need to set the rendering type on the UIImage for this to have any effect.
UIImage *originalImage = [UIImage imageNamed:#"image.png"];
UIImage *tintedImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *imageView = [[UIImageView alloc] initWithImage:tintedImage];
imageView.tintColor = [UIColor grayColor];
[self.view addSubview:imageView];
This should produce the effect you are after in a default state.
If you are setting the image for a button, just go to attributes inspector and change the button type to system. Then set the image and change the tint color. The color of the image will change. If it did not take place, check the button type.
For change tint of image (pick, classical image, photo) use that :
Example image :
Swift 2
public extension UIImage {
/**
Tint, Colorize image with given tint color<br><br>
This is similar to Photoshop's "Color" layer blend mode<br><br>
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<br><br>
<img src="http://yannickstephan.com/easyhelper/tint1.png" height="70" width="120"/>
**To**
<img src="http://yannickstephan.com/easyhelper/tint2.png" height="70" width="120"/>
- parameter tintColor: UIColor
- returns: UIImage
*/
public func tintPhoto(tintColor: UIColor) -> UIImage {
return modifiedImage { context, rect in
// draw black background - workaround to preserve color of partially transparent pixels
CGContextSetBlendMode(context, .Normal)
UIColor.blackColor().setFill()
CGContextFillRect(context, rect)
// draw original image
CGContextSetBlendMode(context, .Normal)
CGContextDrawImage(context, rect, self.CGImage)
// tint image (loosing alpha) - the luminosity of the original image is preserved
CGContextSetBlendMode(context, .Color)
tintColor.setFill()
CGContextFillRect(context, rect)
// mask by alpha values of original image
CGContextSetBlendMode(context, .DestinationIn)
CGContextDrawImage(context, rect, self.CGImage)
}
}
/**
Tint Picto to color
- parameter fillColor: UIColor
- returns: UIImage
*/
public func tintPicto(fillColor: UIColor) -> UIImage {
return modifiedImage { context, rect in
// draw tint color
CGContextSetBlendMode(context, .Normal)
fillColor.setFill()
CGContextFillRect(context, rect)
// mask by alpha values of original image
CGContextSetBlendMode(context, .DestinationIn)
CGContextDrawImage(context, rect, self.CGImage)
}
}
/**
Modified Image Context, apply modification on image
- parameter draw: (CGContext, CGRect) -> ())
- returns: UIImage
*/
private func modifiedImage(#noescape draw: (CGContext, CGRect) -> ()) -> UIImage {
// using scale correctly preserves retina images
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context: CGContext! = UIGraphicsGetCurrentContext()
assert(context != nil)
// correctly rotate image
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
let rect = CGRectMake(0.0, 0.0, size.width, size.height)
draw(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
UPD
Swift 3
extension UIImage {
/**
Tint, Colorize image with given tint color<br><br>
This is similar to Photoshop's "Color" layer blend mode<br><br>
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<br><br>
<img src="http://yannickstephan.com/easyhelper/tint1.png" height="70" width="120"/>
**To**
<img src="http://yannickstephan.com/easyhelper/tint2.png" height="70" width="120"/>
- parameter tintColor: UIColor
- returns: UIImage
*/
func tintPhoto(_ tintColor: 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)
tintColor.setFill()
context.fill(rect)
// mask by alpha values of original image
context.setBlendMode(.destinationIn)
context.draw(context.makeImage()!, in: rect)
}
}
/**
Tint Picto to color
- parameter fillColor: UIColor
- returns: UIImage
*/
func tintPicto(_ fillColor: UIColor) -> UIImage {
return modifiedImage { context, rect in
// draw tint color
context.setBlendMode(.normal)
fillColor.setFill()
context.fill(rect)
// mask by alpha values of original image
context.setBlendMode(.destinationIn)
context.draw(cgImage!, 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!
}
}
You can use this extension:
import UIKit
extension CGContext {
func fill(_ rect: CGRect,
with mask: CGImage,
using color: CGColor) {
saveGState()
defer { restoreGState() }
translateBy(x: 0.0, y: rect.size.height)
scaleBy(x: 1.0, y: -1.0)
setBlendMode(.normal)
clip(to: rect, mask: mask)
setFillColor(color)
fill(rect)
}
}
extension UIImage {
func filled(with color: UIColor) -> UIImage {
let rect = CGRect(origin: .zero, size: self.size)
guard let mask = self.cgImage else { return self }
if #available(iOS 10.0, *) {
let rendererFormat = UIGraphicsImageRendererFormat()
rendererFormat.scale = self.scale
let renderer = UIGraphicsImageRenderer(size: rect.size,
format: rendererFormat)
return renderer.image { context in
context.cgContext.fill(rect,
with: mask,
using: color.cgColor)
}
} else {
UIGraphicsBeginImageContextWithOptions(rect.size,
false,
self.scale)
defer { UIGraphicsEndImageContext() }
guard let context = UIGraphicsGetCurrentContext() else { return self }
context.fill(rect,
with: mask,
using: color.cgColor)
return UIGraphicsGetImageFromCurrentImageContext() ?? self
}
}
}
If you use asset catalogs you can set the image asset itself to render in template mode. After that you can set the tintColor of the button in Interface Builder (or in code) and it should take.
Swift 4
let origImage = UIImage(named: "check")
let tintedImage = origImage?.withRenderingMode(.alwaysTemplate)
buttons[0].setImage(tintedImage, for: .normal)
buttons[0].tintColor = .red
If you use asset catalogs you can set the image asset itself to render in template mode. After that you can set the tintColor of the button in Interface Builder (or in code) and it should take.
Swift 4 and 4.2
let img = UIImage.init(named: "buttonName")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
btn.setImage(img, for: .normal)
btn.tintColor = .gray

Resources