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.
Related
In Swift 2 I used a User Defined Runtime Attribute in Storyboard with a key path of tintColor to change the tab bar item icon colors. However, it looks like tintColor was removed with Swift 3. How can I change the selected color of the tab bar items in a tab bar controller in Swift 3?
Thanks!
EDIT: Attached screenshot
Use tabBarItem.setTitleTextAttributes to change text color of individual bar items.
Put this in viewDidLoad method of each tab:
self.tabBarItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.red()], for:.selected)
To change the icon and text tint color together a simple solution is to change the tabBar tint color in viewWillAppear method of each tab:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.tintColor = UIColor.red()
}
Another solution to change the image tint color is to create an extension for UIImage and use it to change the selected image with custom tint:
extension UIImage {
func tabBarImageWithCustomTint(tintColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
let context: CGContext = UIGraphicsGetCurrentContext()!
context.translate(x: 0, y: self.size.height)
context.scale(x: 1.0, y: -1.0)
context.setBlendMode(CGBlendMode.normal)
let rect: CGRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
context.clipToMask(rect, mask: self.cgImage!)
tintColor.setFill()
context.fill(rect)
var newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
newImage = newImage.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
return newImage
}
}
Use this code to change the selected image:
self.tabBarItem.selectedImage = self.tabBarItem.selectedImage?.tabBarImageWithCustomTint(tintColor: UIColor.red())
The latest code as par Swift 3 is
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.normal)
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
}
}
I created an extension to UIImage and added the function imageWithColor(color: UIColor) that goes like this:
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
}
The problem is that when I display the image (using an UIImageView) it's being displayed with an odd opacity (like alpha 0.5 or something) and I can't understand why.
Any ideas why is it happening?
I am trying to change background color of my UITabBarItem this way:
UITabBar.appearance().selectionIndicatorImage = UIImage.imageWithColor(UIColor.blackColor())
Here's extension for UIImage:
extension UIImage {
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
It does not work. Here's how it should look finally:
Looks like your image is too small let rect = CGRectMake(0.0, 0.0, 1.0, 1.0). Try to replace with let rect = CGRectMake(0.0, 0.0, 50.0, 50.0).
Is there a way to tint the images in an animation?
I know I can tint a single image like this:
var imageOne:UIImage = UIImage(named: "pullto_1.png")!;
imageOne = imageOne.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
refSequence.image = imageOne;
But when I try to do it like this it just dosen't work:
var imageOne:UIImage = UIImage(named: "pullto_1.png")!;
imageOne = imageOne.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
var image2:UIImage = UIImage(named: "pullto_2.png")!;
image2 = image2.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
var image3:UIImage = UIImage(named: "pullto_3.png")!;
image3 = image3.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
var image4:UIImage = UIImage(named: "pullto_4.png")!;
image4 = image4.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
refSequence.animationImages = NSArray(objects: imageOne,
image2,
image3,
image4
);
refSequence.animationDuration = 1.4;
refSequence.animationRepeatCount = 99;
refSequence.startAnimating();
Am I doing something wrong? Is there some way to tint the images in the animation?
Thanks
Ok, i hoped that there is a simpler solution but this is what I ended up doing:
This function will create a new image with the wanted color:
func imageWithColor(img:UIImage, color:UIColor)->UIImage{
UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale);
var context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeNormal);
var rect = CGRectMake(0, 0, img.size.width, img.size.height);
CGContextClipToMask(context, rect, img.CGImage)
color.setFill();
CGContextFillRect(context, rect);
var newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
And then you can call it like this:
var imageOne:UIImage = UIImage(named: "pullto_1.png")!;
imageOne = imageWithColor(imageOne, color: UIColor.redColor());
var image2:UIImage = UIImage(named: "pullto_2.png")!;
image2 = imageWithColor(image2, color: UIColor.redColor());
var image3:UIImage = UIImage(named: "pullto_3.png")!;
image3 = imageWithColor(image3, color: UIColor.redColor());
var image4:UIImage = UIImage(named: "pullto_4.png")!;
image4 = imageWithColor(image4, color: UIColor.redColor());
loaderS.animationImages = NSArray(objects: imageOne,
image2,
image3,
image4
);
loaderS.animationDuration = 1.4;
loaderS.animationRepeatCount = 99;
loaderS.startAnimating();
Here is a handy UIImage extension:
import UIKit
extension UIImage {
func imageWithTint(tint: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, 0, size.height)
CGContextScaleCTM(context, 1.0, -1.0)
CGContextSetBlendMode(context, .Normal)
let rect = CGRect(origin: .zero, size: size)
CGContextClipToMask(context, rect, CGImage)
tint.setFill()
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image;
}
}
There's a rdar about this issue (http://www.openradar.me/23517334) and the problem still persists on iOS 11.
I adapted the code examples above to Swift 4.
extension UIImage {
func image(withTintColor color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let context = UIGraphicsGetCurrentContext()
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)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
For Swift 5: Create image with color you want with below function. Then use those images to set to your image view's animation property:
extension UIImage {
func imageWithColor(_ color: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
guard let context = UIGraphicsGetCurrentContext(), let cgImage = self.cgImage else { return nil }
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: size.width, height: size.height)
context.clip(to: rect, mask: cgImage)
color.setFill()
context.fill(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return newImage
}
}
let animImages = [
image0.imageWithColor(color),
image1.imageWithColor(color),
image2.imageWithColor(color),
].compactMap({ $0 })
imageView.animationImages = animImages
imageView.animationDuration = 0.7
imageView.animationRepeatCount = 0
Here's updated code for Swift 4 with a few safety checks.
extension UIImage {
func image(withTint tint: UIColor) -> UIImage? {
guard let cgImage = cgImage else {
return nil
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
let rect = CGRect(origin: .zero, size: size)
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setBlendMode(.normal)
context.clip(to: rect, mask: cgImage)
tint.setFill()
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
Maybe this example extension helps:
extension UIImageView {
func pulsingTintColor() {
UIView.animate(withDuration: 2, delay: 0.0, options: [.repeat, .autoreverse], animations: {
self.tintColor = UIColor.red
self.tintColor = UIColor.green
self.tintColor = UIColor.blue
}, completion: nil)
}
}
Ensure you have set the Render as: Template Image option in your asset catalog. This works for UIViews as well. Just replace tintColor with backgroundColor.
If you need parametrised colours:
func pulsingTintColor(with colors: [UIColor] = [UIColor.red, UIColor.green, UIColor.blue]) {
UIView.animate(withDuration: 2, delay: 0.0, options: [.repeat, .autoreverse], animations: {
colors.forEach({self.tintColor = $0})
}, completion: nil)
}
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
}
}