Related
I have an UITabBar that each tab changes its color on tap.
I want it to be animated (0.5 seconds between notSelectedColor to SelectedColor), how can I do that?
I'm re-drawing the image with the color like that:
func imageWithColor(color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, 0.0, size.height)
CGContextScaleCTM(context, 1.0, -1.0)
CGContextSetBlendMode(context, CGBlendMode.Normal)
let rect = CGRect(origin: CGPointZero, size: size)
CGContextClipToMask(context, rect, CGImage)
color.setFill()
CGContextFillRect(context, rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
return newImage
}
thank you!
You can animate the tab color by following code:
let tabBar: UITabBar? = self.tabBarController?.tabBar
UIView.transitionWithView(tabBar!, duration: 1.0, options: [.BeginFromCurrentState, .TransitionCrossDissolve], animations: {
self.tabBarController?.tabBar.tintColor = UIColor.purpleColor()
}, completion: nil)
I have also made a sample project for you. Download the sample project here.
I'm trying out this code to tint color a UIImage but it seems to be inverting the colors - coloring the background and turning the black stroke to white.
How do I have it color the black stroke lines of an image and leave the white background alone? Here's a playground sample where I try one technique as well as another one suggested by the first answer.
Playground Code
extension UIImage {
func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
let context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, blendMode);
let rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextClipToMask(context, rect, self.CGImage);
color.setFill()
CGContextFillRect(context, rect);
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
func tint2(color: UIColor, blendMode: CGBlendMode) -> UIImage
{
let drawRect = CGRectMake(0.0, 0.0, size.width, size.height)
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()
CGContextClipToMask(context, drawRect, CGImage)
color.setFill()
UIRectFill(drawRect)
drawInRect(drawRect, blendMode: blendMode, alpha: 1.0)
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return tintedImage
}
}
let stringUrl = "https://s3.amazonaws.com/neighbor-chat-assets/icons/avatar1000.png"
let url = NSURL(string: stringUrl)
let data = NSData(contentsOfURL: url!)
var originalimage = UIImage(data: data!)
var image = originalimage!.imageWithRenderingMode(.AlwaysTemplate).tint(UIColor.blueColor(), blendMode:.Normal)
var image2 = originalimage!.imageWithRenderingMode(.AlwaysTemplate).tint2(UIColor.blueColor(), blendMode:.Normal)
This should work, I just tested it with a png and transparent background
func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
let context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, blendMode);
let rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextClipToMask(context, rect, self.CGImage);
color.setFill()
CGContextFillRect(context, rect);
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Also see this post for further information. The alternative would be to wrap your image in a UIImageView with UIImageRenderingMode.AlwaysTemplate rendering mode, and then set the tintColor property.
EDIT
So this is the function you can use to first mask out certain colors if you don't have an alpha channel in an image
func tint(color: UIColor, blendMode: CGBlendMode = CGBlendMode.Normal, colorToMask: UIColor = UIColor.blackColor()) -> UIImage
{
var fRed : CGFloat = 0
var fGreen : CGFloat = 0
var fBlue : CGFloat = 0
var fAlpha: CGFloat = 0
colorToMask.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha)
let rect = CGRectMake(0, 0, self.size.width, self.size.height);
var colorMasking : [CGFloat] = [fRed,fRed,fGreen,fGreen,fBlue,fBlue]
let newImageRef = CGImageCreateWithMaskingColors(self.CGImage!, &colorMasking)
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
let context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, blendMode);
CGContextClipToMask(context, rect, newImageRef!);
color.setFill()
CGContextFillRect(context, rect);
let newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
This function first creates a mask depending on the specified color, and clips the context to this mask.
EDIT #2
As stated in the comments, I also didn't manage to mask out the white color, I tried UInt8 values (i.e. 255) and float values (i.e. 1.0), but it still didn't work. So the only solution I could come up with was to invert the image. Here is the function:
func negativeImage() -> UIImage {
let coreImage = MYImage(CGImage: self.CGImage!)
let filter = CIFilter(name: "CIColorInvert", withInputParameters: ["inputImage" : coreImage])!
let result = filter.outputImage!
let context = CIContext(options:nil)
let cgimg : CGImageRef = context.createCGImage(result, fromRect: result.extent)
let bmpcontext = CGBitmapContextCreate(nil, Int(self.size.width), Int(self.size.height), 8, Int(self.size.width)*4, CGColorSpaceCreateDeviceRGB(),CGImageAlphaInfo.NoneSkipLast.rawValue)
CGContextDrawImage(bmpcontext, CGRectMake(0, 0, self.size.width, self.size.height), cgimg)
let newImgRef = CGBitmapContextCreateImage(bmpcontext)!
return UIImage(CGImage: newImgRef)
}
You can use it like this
var image : UIImage = ...
image = image.negativeImage()
image = image.tint(UIColor.redColor())
Here are some general notes
CGImageCreateWithMaskingColors requires a CGImage with NO alpha channel, that's why I use the flag CGImageAlphaInfo.NoneSkipLast in the negativeImage function in order to remove the alpha channel again
There is absolutely no error checking included in this, you should at least check whether there is an alpha channel included within the tint function using CGImageGetAlphaInfo
You can either have the masked out parts (white in our example) transparent (by setting false in the UIGraphicsBeginImageContextWithOptions command within the tint function), or a specific color by setting true, which is black by default (you would need to paint before clipping to the mask).
I would like to color a white icon to another color in run time, I was trying to use the method taken from here, but without success:
func maskImageView() {
var maskImageSize = CGSizeMake(self.downloadImageView.frame.width, self.downloadImageView.frame.height)
UIGraphicsBeginImageContextWithOptions(maskImageSize, false, 0.0)
var color = UIColor(white: 1.0, alpha: 1.0)
color.setFill()
var rect = CGRectMake(0, 0, self.downloadImageView.frame.width, self.downloadImageView.frame.height)
UIRectFill(rect)
color = BrandColors.BRAND_FIRST_COLOR
color.setFill()
rect = CGRectMake((self.downloadImageView.frame.width/2)-100, (self.downloadImageView.frame.height/2)-100, 200, 200)
UIRectFill(rect)
var maskImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
var maskLayer = CALayer()
maskLayer.frame = CGRectMake(0, 0, self.downloadImageView.bounds.width, self.downloadImageView.bounds.height)
maskLayer.contents = maskImage.CGImage
maskLayer.contentsRect = CGRectMake(0, 0, self.downloadImageView.bounds.width, self.downloadImageView.bounds.height)
self.downloadImageView.layer.mask = maskLayer;
}
I can't actually figure out how this masking thing works. What am I doing wrong?
I was able to achieve this task with the following method:
func maskDownloadImageView() {
downloadImageView.image = downloadImageView.image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
downloadImageView.tintColor = BrandColors.BRAND_FIRST_COLOR
}
There is no need in using masking.
Try this.
func fillImage(image : UIImage! ,withColor color : UIColor!) -> UIImage!
{
// Create the proper sized rect
let imageRect = CGRectMake(0, 0, image.size.width, image.size.height)
// Create a new bitmap context
let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
let context = CGBitmapContextCreate(nil, Int(imageRect.size.width), Int(imageRect.size.height), 8, 0, colorSpace, bitmapInfo)
// Use the passed in image as a clipping mask
CGContextClipToMask(context, imageRect, image.CGImage)
// Set the fill color
CGContextSetFillColorWithColor(context, color.CGColor)
// Fill with color
CGContextFillRect(context, imageRect)
// Generate a new image
let newCGImage = CGBitmapContextCreateImage(context)
let newImage = UIImage(CGImage: newCGImage)
return newImage;
}
To mask transparent image try this:
func maskImageWithColor(color : UIColor!) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
if let context: CGContextRef = UIGraphicsGetCurrentContext() {
CGContextTranslateCTM(context, 0, self.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
CGContextSetBlendMode(context, .Normal)
let rect: CGRect = CGRectMake(0, 0, self.size.width, self.size.height)
// To use gradient, add CGColor to Array
let colors: [CGColor] = [color.CGColor]
let colorsPointer = UnsafeMutablePointer<UnsafePointer<Void>>(colors)
if let colorsCFArray = CFArrayCreate(nil, colorsPointer, colors.count, nil) {
if let space: CGColorSpaceRef = CGColorSpaceCreateDeviceRGB() {
if let gradient: CGGradientRef = CGGradientCreateWithColors(space, colorsCFArray, nil) {
// Apply gradient
CGContextClipToMask(context, rect, self.CGImage)
CGContextDrawLinearGradient(context, gradient, CGPointMake(0,0), CGPointMake(0, self.size.height), .DrawsBeforeStartLocation)
let coloredImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage;
}
}
}
}
return self
}
You can use gradient just adding more CGColor in the array
Swift 4 version of Emil Adz answer for the lazy.
func maskDownloadImageView() {
downloadImageView.image = downloadImageView.image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
downloadImageView.tintColor = BrandColors.BRAND_FIRST_COLOR
}
I try to create an image from a color ( tutorial ) but it does not work for me : this code does not show any image:
import UIKit
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let image = ViewController.imageFromColor(UIColor.redColor(), size: CGSizeMake(320,49), radius: 0)
println("image \(image.size)")
let imageView = UIImageView(image: image)
imageView.center = self.view.center
self.view.addSubview(imageView)
}
class func imageFromColor(color:UIColor, size:CGSize, radius:CGFloat)->UIImage
{
let rect = CGRectMake(0,0, size.width, size.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
var image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIGraphicsBeginImageContext(size)
UIBezierPath(roundedRect: rect, cornerRadius: radius).addClip()
image.drawInRect(rect)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
The image size in the log is 320,49, the viewController is well connected between the storyboard and the file.
Thanks
I think it is missing CGContextFillRect(context, rect);
Try
let rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
let context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
var image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContext(size)
UIBezierPath(roundedRect: rect, cornerRadius: radius).addClip()
image.drawInRect(rect)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image;
It is well known that the tint color of selected (or active) items in a UITabBarController can be easily changed, here is an example:
myBarController.tabBar.tintColor = [UIColor redColor];
In this instance, any tab bar item in tabBar will have a red tint once it is made active. Again, this applies to all of the items in this tab bar.
How can the active tint color be different between other tab bar items in the same bar? For example, one item might have a red tint while selected, while another might have a blue tint.
I am aware that this can probably be solved by redrawing and subclassing the entire tab bar. However, this is the only change I need, and it seems overkill to do so. I'm not trying to change the style or how the items are rendered in any way, just to make that style different between different items.
I haven't seen any answers to this question anywhere that are relevant to the updates in iOS 7 and 8.
There is a much easier way to do this!
Add this to the ViewController which UITabBar Item should be in another color
- (void) viewWillAppear:(BOOL)animated {
// change tint color to red
[self.tabBarController.tabBar setTintColor:[UIColor redColor]];
[super viewWillAppear: animated];
}
Insert this to the other ViewControllers
- (void) viewWillAppear:(BOOL)animated {
// change tint color to black
[self.tabBarController.tabBar setTintColor:[UIColor blackColor]];
[super viewWillAppear: animated];
}
I use this to get different Tint colors in each ViewController
e.g.: [ red | black | green | pink ]
#element119 solution using swift(for you lazy guys):
extension UIImage {
func tabBarImageWithCustomTint(tintColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
let context: CGContextRef = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, 0, self.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
let rect: CGRect = CGRectMake(0, 0, self.size.width, self.size.height)
CGContextClipToMask(context, rect, self.CGImage)
tintColor.setFill()
CGContextFillRect(context, rect)
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
newImage = newImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
return newImage
}
}
I'm using this code to tint my middle icon red:
if let items = self.tabBar.items as? [UITabBarItem] {
let button = items[1]
button.image = button.image?.tabBarImageWithCustomTint(UIColor.redColor())
}
I did some experimenting and based on this answer, found a way to do what I want without subclassing UITabBarItem or UITabBar!
Basically, the idea is to create a method of UIImage that mimics the tint mask behavior of UITabBar, while rendering it in its "original" form and avoiding the native tint mask.
All you have to do is create a new instance method of UIImage that returns an image masked with the color we want:
#interface UIImage(Overlay)
- (instancetype)tabBarImageWithCustomTint:(UIColor *)tintColor;
#end
#implementation UIImage(Overlay)
- (instancetype)tabBarImageWithCustomTint:(UIColor *)tintColor
{
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);
[tintColor setFill];
CGContextFillRect(context, rect);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
newImage = [newImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
return newImage;
}
#end
This is a fairly straightforward version of the code in the answer that I posted, with one exception- the returned image has its rendering mode set to always original, which makes sure that the default UITabBar mask won't be applied. Now, all that is needed is to use this method when editing the tab bar item:
navController.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"title" image:normal_image selectedImage:[selected_image tabBarImageWithCustomTint:[UIColor redColor]]];
Needless to say, selected_image is the normal image one gets from UIImage imageNamed: and the [UIColor redColor can be replaced with any color one desires.
This worked fine for me!! code for Swift 3
extension UIImage {
func tabBarImageWithCustomTint(tintColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
let context: CGContext = UIGraphicsGetCurrentContext()!
context.translateBy(x: 0, y: self.size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(CGBlendMode(rawValue: 1)!)
let rect: CGRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context.clip(to: rect, mask: self.cgImage!)
tintColor.setFill()
context.fill(rect)
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
newImage = newImage.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
return newImage
}
}
and after...
button.image = button.image?.tabBarImageWithCustomTint(tintColor: UIColor(red: 30.0/255.0, green: 33.0/255.0, blue: 108.0/255.0, alpha: 1.0))
thanks ;))
swift xcode7.1 tested :
extension UIImage {
func tabBarImageWithCustomTint(tintColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
let context: CGContextRef = UIGraphicsGetCurrentContext()!
CGContextTranslateCTM(context, 0, self.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
CGContextSetBlendMode(context, CGBlendMode.Normal)
let rect: CGRect = CGRectMake(0, 0, self.size.width, self.size.height)
CGContextClipToMask(context, rect, self.CGImage)
tintColor.setFill()
CGContextFillRect(context, rect)
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
newImage = newImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
return newImage
}
}
fixed the compatibility bug in #Binsh answer
Swift 5:
extension UIImage {
func tintWithColor(color: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -self.size.height)
context.setBlendMode(.multiply)
let rect = CGRect(origin: .zero, size: size)
guard let cgImage = self.cgImage else { return nil }
context.clip(to: rect, mask: cgImage)
color.setFill()
context.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}