Progressview tintColorIssue - ios

I have created a progressview according to number images as you can see in below code.
let view = UIView()
view.backgroundColor = .clear
let progressView = UIProgressView(frame: CGRect(x: 0, y: 0, width: frameOfParentView.width/3 - 8, height: 30))
progressView.progressViewStyle = .default
progressView.progress = 0.0
progressView.tintColor = .red
progressView.trackTintColor = .gray
progressView.layoutIfNeeded()
view.addSubview(progressView)
self.arrayOfProgrssView.append(progressView)
As you can see in gif at starting point tintColor alpha is little bit less but when it tense to reach at 100% it is fully red.
I also tried with below code:-
progressView.progressTintColor = .red
but did not get expected result.
To perform animation,
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
UIView.animate(withDuration: self.animationInMS) {
progressView.setProgress(1, animated: true)
}
}
progressView.layoutIfNeeded()
Issue in iOS 15:-
As you see below result with other colour.
Note:- I have checked in iOS 12.4 it's working properly as you can see into image.
Please let me know is anything require from my side.
Thanks in advance

This does appear to be "new behavior" where the alpha value matches the percent completion -- although, after some quick searching I haven't found any documentation on it.
One option as a work-around: set the .progressImage instead of the tint color.
So, use your favorite code to generate a solid-color image, such as:
extension UIImage {
public static func withColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let image = UIGraphicsImageRenderer(size: size, format: format).image { rendererContext in
color.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
return image
}
}
Then, instead of:
progressView.tintColor = .red
use:
let img = UIImage.withColor(.red)
progressView.progressImage = img
Not fully tested, but to avoid the need to change existing code, you might also try:
extension UIProgressView {
open override var tintColor: UIColor! {
didSet {
let img = UIImage.withColor(tintColor)
progressImage = img
}
}
}
Now you can keep your existing progressView.tintColor = .red

Related

Change White Pixels in a UIImage

I have a UIImage which is an outline, and one which is filled, both were created from OpenCV, one from grab cut and the other from structuring element.
my two images are like this:
I am trying to change all of the white pixels in the outlined image because I want to merge the two images together so I end up with a red outline and a white filled area in the middle. I am using this to merge the two together, and I am aware instead of red it will be a pink kind of colour and a grey instead of white since I am just blending them together with alpha.
// Start an image context
UIGraphicsBeginImageContext(grabcutResult.size)
// Create rect for this draw session
let rect = CGRect(x: 0.0, y: 0.0, width: grabcutResult.size.width, height: grabcutResult.size.height)
grabcutResult.draw(in: rect)
redGrabcutOutline.draw(in: rect, blendMode: .normal, alpha: 0.5)
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
and the idea is it should look something like this.
I want to be able to complete this operation quickly but the only solutions I have found are either for ImageView (which only affects how stuff is rendered not the underlying UIImage) or they involve looping over the entire image pixel by pixel.
I am trying to find a solution that will just mask all the white pixels in the outline to red without having to loop over the entire image pixel by pixel as it is too slow.
Ideally it would be good if I could get openCV to just return a red outline instead of white but I don't think its possible to change this (maybe im wrong).
Using swift btw... but any Help is appreciated, thanks in advance.
This may work for you - using only Swift code...
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskingColors: [CGFloat] = [1, 255, 1, 255, 1, 255]
let bounds = CGRect(origin: .zero, size: size)
let maskImage = cgImage!
var returnImage: UIImage?
// make sure image has no alpha channel
let rFormat = UIGraphicsImageRendererFormat()
rFormat.opaque = true
let renderer = UIGraphicsImageRenderer(size: size, format: rFormat)
let noAlphaImage = renderer.image {
(context) in
self.draw(at: .zero)
}
let noAlphaCGRef = noAlphaImage.cgImage
if let imgRefCopy = noAlphaCGRef?.copy(maskingColorComponents: maskingColors) {
let rFormat = UIGraphicsImageRendererFormat()
rFormat.opaque = false
let renderer = UIGraphicsImageRenderer(size: size, format: rFormat)
returnImage = renderer.image {
(context) in
context.cgContext.clip(to: bounds, mask: maskImage)
context.cgContext.setFillColor(color.cgColor)
context.cgContext.fill(bounds)
context.cgContext.draw(imgRefCopy, in: bounds)
}
}
return returnImage
}
}
This extension returns a UIImage with white replaced with the passed UIColor, and the black "background" changed to transparent.
Use it in this manner:
// change filled white star to gray with transparent background
let modFilledImage = filledImage.maskWithColor(color: UIColor(red: 200, green: 200, blue: 200))
// change outlined white star to red with transparent background
let modOutlineImage = outlineImage.maskWithColor(color: UIColor.red)
// combine the images on a black background
Here is a full example, using your two original images (most of the code is setting up image views to show the results):
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskingColors: [CGFloat] = [1, 255, 1, 255, 1, 255]
let bounds = CGRect(origin: .zero, size: size)
let maskImage = cgImage!
var returnImage: UIImage?
// make sure image has no alpha channel
let rFormat = UIGraphicsImageRendererFormat()
rFormat.opaque = true
let renderer = UIGraphicsImageRenderer(size: size, format: rFormat)
let noAlphaImage = renderer.image {
(context) in
self.draw(at: .zero)
}
let noAlphaCGRef = noAlphaImage.cgImage
if let imgRefCopy = noAlphaCGRef?.copy(maskingColorComponents: maskingColors) {
let rFormat = UIGraphicsImageRendererFormat()
rFormat.opaque = false
let renderer = UIGraphicsImageRenderer(size: size, format: rFormat)
returnImage = renderer.image {
(context) in
context.cgContext.clip(to: bounds, mask: maskImage)
context.cgContext.setFillColor(color.cgColor)
context.cgContext.fill(bounds)
context.cgContext.draw(imgRefCopy, in: bounds)
}
}
return returnImage
}
}
class MaskWorkViewController: UIViewController {
let origFilledImgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .center
return v
}()
let origOutlineImgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .center
return v
}()
let modifiedFilledImgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .center
return v
}()
let modifiedOutlineImgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .center
return v
}()
let combinedImgView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .center
return v
}()
let origStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.spacing = 20
return v
}()
let modifiedStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.spacing = 20
return v
}()
let mainStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .center
v.spacing = 10
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
guard let filledImage = UIImage(named: "StarFill"),
let outlineImage = UIImage(named: "StarEdge") else {
return
}
var modifiedFilledImage: UIImage = UIImage()
var modifiedOutlineImage: UIImage = UIImage()
var combinedImage: UIImage = UIImage()
// for both original images, replace white with color
// and make black transparent
if let modFilledImage = filledImage.maskWithColor(color: UIColor(red: 200, green: 200, blue: 200)),
let modOutlineImage = outlineImage.maskWithColor(color: UIColor.red) {
modifiedFilledImage = modFilledImage
modifiedOutlineImage = modOutlineImage
let rFormat = UIGraphicsImageRendererFormat()
rFormat.opaque = true
let renderer = UIGraphicsImageRenderer(size: modifiedFilledImage.size, format: rFormat)
// combine modified images on black background
combinedImage = renderer.image {
(context) in
context.cgContext.setFillColor(UIColor.black.cgColor)
context.cgContext.fill(CGRect(origin: .zero, size: modifiedFilledImage.size))
modifiedFilledImage.draw(at: .zero)
modifiedOutlineImage.draw(at: .zero)
}
}
// setup image views and set .image properties
setupUI(filledImage.size)
origFilledImgView.image = filledImage
origOutlineImgView.image = outlineImage
modifiedFilledImgView.image = modifiedFilledImage
modifiedOutlineImgView.image = modifiedOutlineImage
combinedImgView.image = combinedImage
}
func setupUI(_ imageSize: CGSize) -> Void {
origStack.addArrangedSubview(origFilledImgView)
origStack.addArrangedSubview(origOutlineImgView)
modifiedStack.addArrangedSubview(modifiedFilledImgView)
modifiedStack.addArrangedSubview(modifiedOutlineImgView)
var lbl = UILabel()
lbl.textAlignment = .center
lbl.text = "Original Images"
mainStack.addArrangedSubview(lbl)
mainStack.addArrangedSubview(origStack)
lbl = UILabel()
lbl.textAlignment = .center
lbl.numberOfLines = 0
lbl.text = "Modified Images\n(UIImageViews have Green Background)"
mainStack.addArrangedSubview(lbl)
mainStack.addArrangedSubview(modifiedStack)
lbl = UILabel()
lbl.textAlignment = .center
lbl.text = "Combined on Black Background"
mainStack.addArrangedSubview(lbl)
mainStack.addArrangedSubview(combinedImgView)
view.addSubview(mainStack)
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
mainStack.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
[origFilledImgView, origOutlineImgView, modifiedFilledImgView, modifiedOutlineImgView, combinedImgView].forEach {
$0.backgroundColor = .green
NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalToConstant: imageSize.width),
$0.heightAnchor.constraint(equalToConstant: imageSize.height),
])
}
}
}
And the result, showing the original, modified and final combined image... Image views have green backgrounds to show the transparent areas:
The idea is to bitwise-or the two masks together which "merges" the two masks. Since this new combined grayscale image is still a single (1-channel) image, we need to convert it to a 3-channel so we can apply color to the image. Finally, we color the outline mask with red to get our result
I implemented it in Python OpenCV but you can adapt the same idea with Swift
import cv2
# Read in images as grayscale
full = cv2.imread('1.png', 0)
outline = cv2.imread('2.png', 0)
# Bitwise-or masks
combine = cv2.bitwise_or(full, outline)
# Combine to 3 color channel and color outline red
combine = cv2.merge([combine, combine, combine])
combine[outline > 120] = (57,0,204)
cv2.imshow('combine', combine)
cv2.waitKey()
Benchmarks using IPython
In [3]: %timeit combine()
782 µs ± 10.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Using the Vectorized feature of Numpy, it seems to be pretty fast
try this:
public func maskWithColor2( color:UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()!
color.setFill()
context.translateBy(x: 0, y: self.size.height)
context.scaleBy(x: 1.0, y: -1.0)
let rect = CGRect(x: 0.0, y: 0.0, width: self.size.width, height: self.size.height)
context.draw(self.cgImage!, in: rect)
context.setBlendMode(CGBlendMode.sourceIn)
context.addRect(rect)
context.drawPath(using: CGPathDrawingMode.fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage!
}

Create a masked UIView

I am attempting to do something that I thought would be possible, but have no idea how to start.
I have created a UIView and would like it to be filled with colour, but in the shape defined by an image mask. i.e. I have a PNG with alpha, and I would like the UIView I have created to be UIColor.blue in the shape of that PNG.
To show just how stupid I am, here is the absolute rubbish I have attempted so far - trying to generate just a simple square doesn't even work for me, hence it's all commented out.
let rocketColourList: [UIColor] = [UIColor.blue, UIColor.red, UIColor.green, UIColor.purple, UIColor.orange]
var rocketColourNum: Int = 0
var rocketAngleNum: Int = 0
var rocketAngle: Double {
return Double(rocketAngleNum) * Double.pi / 4
}
var rocketColour = UIColor.black
public func drawRocket (){
rocketColour.set()
self.clipsToBounds = true
self.backgroundColor = UIColor.red
imageView.image = UIImage(named: ("rocketMask"))
addSubview(imageView)
//maskimage.frame = self.frame
//let someRect = CGRect (x: 1, y: 1, width: 1000, height: 1000)
//let someRect = CGRect (self.frame)
//let fillPath = UIBezierPath(rect:someRect)
//fillPath.fill()
//setNeedsDisplay()
}
}
Set image as template, then set tint color for UIImageView. Something like that:
guard let let image = UIImage(named: "rocketMask")?.withRenderingMode(.alwaysTemplate) else { return }
imageView.image = image
imageView.tintColor = .blue
Also you can do that through the images assets:

Swift 3 - full width of UITabBarItem for selectionIndicatorImage

i have a UITabBar with three tabs. Now I want to assign or lets say to fill the complete width of one tab to the related selectionIndicatorImage cause currently I got a border if a tab is selected. Like the tab on the left side shows in the following screenshot:
I made a subclass of UITabBar with a new property:
var activeItemBackground:UIColor = UIColor.white {
didSet {
let numberOfItems = CGFloat((items!.count))
let tabBarItemSize = CGSize(width: frame.width / numberOfItems,
height: frame.height)
selectionIndicatorImage = UIImage.imageWithColor(color: activeItemBackground,
size: tabBarItemSize).resizableImage(withCapInsets: .zero)
frame.size.width = frame.width + 4
frame.origin.x = -2
}
}
And the UIImage-Extension in order to have backgroundColor and an image:
extension UIImage
{
class func imageWithColor(color: UIColor, size: CGSize) -> UIImage
{
let rect: CGRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
I read much stuff about this problem but unfortunately I can't get it to work. Is something missing in my code?
I think you're taking a couple extra steps...
You are calculating the exact size of the tab bar item, and creating an image of that size, so you shouldn't need the .resizableImage part.
And, since you are setting to exact size, you also shouldn't need to resize the tab bar frame.
This appears to work fine in my testing (using your .imageWithColor func):
class MyTabBar: UITabBar {
var activeItemBackground:UIColor = UIColor.white {
didSet {
let numberOfItems = CGFloat((items!.count))
let tabBarItemSize = CGSize(width: frame.width / numberOfItems,
height: frame.height)
selectionIndicatorImage = UIImage.imageWithColor(color: activeItemBackground,
size: tabBarItemSize)
}
}
}
Then in viewDidLoad of the first VC:
if let tb = self.tabBarController?.tabBar as? MyTabBar {
tb.activeItemBackground = UIColor.red
}

Change navigation bar bottom border color Swift

It works with
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UIColor {
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
let ctx = UIGraphicsGetCurrentContext()
self.setFill()
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
But when I add a UITableView it doesn't appear on it and when I add a UISearchView it appears but removes the navigation bar.
Anyone knows how to solve this?
You have to adjust the shadowImage property of the navigation bar.
Try this one. I created a category on UIColor as an helper, but you can refactor the way you prefer.
extension UIColor {
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
let ctx = UIGraphicsGetCurrentContext()
self.setFill()
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Option 1: on a single navigation bar
And then in your view controller (change the UIColor to what you like):
// We can use a 1px image with the color we want for the shadow image
self.navigationController?.navigationBar.shadowImage = UIColor.redColor().as1ptImage()
// We need to replace the navigation bar's background image as well
// in order to make the shadowImage appear. We use the same 1px color tecnique
self.navigationController?.navigationBar.setBackgroundImage(UIColor.yellowColor‌​().as1ptImage(), forBarMetrics: .Default)
Option 2: using appearance proxy, on all navigation bars
Instead of setting the background image and shadow image on each navigation bar, it is possible to rely on UIAppearance proxy. You could try to add those lines to your AppDelegate, instead of adding the previous ones in the viewDidLoad.
// We can use a 1px image with the color we want for the shadow image
UINavigationBar.appearance().shadowImage = UIColor.redColor().as1ptImage()
// We need to replace the navigation bar's background image as well
// in order to make the shadowImage appear. We use the same 1px color technique
UINavigationBar.appearance().setBackgroundImage(UIColor.yellowColor().as1ptImage(), forBarMetrics: .Default)
Wonderful contributions from #TheoF, #Alessandro and #Pavel.
Here is what I did for...
Swift 4
extension UIColor {
/// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
///
/// - Returns: `self` as a 1x1 `UIImage`.
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
setFill()
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return image
}
}
Using it in viewDidLoad():
/* In this example, I have a ViewController embedded in a NavigationController in IB. */
// Remove the background color.
navigationController?.navigationBar.setBackgroundImage(UIColor.clear.as1ptImage(), for: .default)
// Set the shadow color.
navigationController?.navigationBar.shadowImage = UIColor.gray.as1ptImage()
Putting #alessandro-orru's answer in one extension
extension UINavigationController {
func setNavigationBarBorderColor(_ color:UIColor) {
self.navigationBar.shadowImage = color.as1ptImage()
}
}
extension UIColor {
/// Converts this `UIColor` instance to a 1x1 `UIImage` instance and returns it.
///
/// - Returns: `self` as a 1x1 `UIImage`.
func as1ptImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
setFill()
UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
UIGraphicsEndImageContext()
return image
}
}
then in your view controller just add:
self.navigationController?.setNavigationBarBorderColor(UIColor.red)
From iOS 13 on, you can use the UINavigationBarAppearance() class with the shadowColor property:
if #available(iOS 13.0, *) {
let style = UINavigationBarAppearance()
style.shadowColor = UIColor.clear // Effectively removes the border
navigationController?.navigationBar.standardAppearance = style
// Optional info for follow-ups:
// The above will override other navigation bar properties so you may have to assign them here, for example:
//style.buttonAppearance.normal.titleTextAttributes = [.font: UIFont(name: "YourFontName", size: 17)!]
//style.backgroundColor = UIColor.orange
//style.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont(name: "AnotherFontName", size: 20.0)!]
} else {
// Fallback on earlier versions
}
Solution for Swift 4.0 - 5.2
Here is small extension for changing both Height and Color of bottom navbar line
extension UINavigationController
{
func addCustomBottomLine(color:UIColor,height:Double)
{
//Hiding Default Line and Shadow
navigationBar.setValue(true, forKey: "hidesShadow")
//Creating New line
let lineView = UIView(frame: CGRect(x: 0, y: 0, width:0, height: height))
lineView.backgroundColor = color
navigationBar.addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
lineView.widthAnchor.constraint(equalTo: navigationBar.widthAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
lineView.centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
}
}
And after adding this extension, you can call this method on any UINavigationController (e.g. from ViewController viewDidLoad())
self.navigationController?.addCustomBottomLine(color: UIColor.black, height: 20)
For iOS 13 and later
guard let navigationBar = navigationController?.navigationBar else { return }
navigationBar.isTranslucent = true
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundImage = UIImage()
appearance.backgroundColor = .clear
navigationBar.standardAppearance = appearance
} else {
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
}
for Swift 3.0 just change this line:
CGContextFillRect(ctx, CGRect(x: 0, y: 0, width: 1, height: 1))
to this:
ctx?.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
There's a much better option available these days:
UINavigationBar.appearance().shadowImage = UIImage()

how to make UITabBar selection indicator image fill the whole space? [duplicate]

This question already has answers here:
Set background color of active tab bar item in Swift
(3 answers)
IOS 8 Tab Bar Item Background Colour
(5 answers)
Closed 10 days ago.
I have a UITabBarController where I use this code to set selection indicator image:
let selectedBG = UIImage(named:"tabbarbgtest.png")?.resizableImageWithCapInsets(UIEdgeInsetsMake(0, 0, 0, 0))
UITabBar.appearance().selectionIndicatorImage = selectedBG
But the image does not fill the whole space - see image below:
The image is just a red square with a solution on 82x49px, but with a wider image it still does not fill the whole space. Hope you guys can help - thanks.
As of 2017, the other answers didn't work for me. After a couple of days searching for another solution, I found mine - by subclassing the UITabBarController.
It works for multiple devices even with rotation.
Notes:
Make your images' rendering mode as Original.
Assign this class below to your UITabBarController in your Storyboard or as your base class if you're doing your screen programmatically.
//
// BaseTabBarController.swift
// MyApp
//
// Created by DRC on 1/27/17.
// Copyright © 2017 PrettyITGirl. All rights reserved.
//
import UIKit
class BaseTabBarController: UITabBarController {
let numberOfTabs: CGFloat = 4
let tabBarHeight: CGFloat = 60
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateSelectionIndicatorImage()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
updateSelectionIndicatorImage()
}
func updateSelectionIndicatorImage() {
let width = tabBar.bounds.width
var selectionImage = UIImage(named:"myimage.png")
let tabSize = CGSize(width: width/numberOfTabs, height: tabBarHeight)
UIGraphicsBeginImageContext(tabSize)
selectionImage?.draw(in: CGRect(x: 0, y: 0, width: tabSize.width, height: tabSize.height))
selectionImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tabBar.selectionIndicatorImage = selectionImage
}
}
To support iPhone X(below code works for all versions), write your code in viewDidLayoutSubviews().
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tabWidth = (tabBar.frame.width/CGFloat(tabBar.items!.count))
let tabHeight = tabBar.frame.height
self.tabBar.selectionIndicatorImage = imageWithColor(color: UIColor.white, size: CGSize(width: tabWidth, height: tabHeight)).resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
}
Source: https://github.com/Ramotion/animated-tab-bar/issues/191
This relatively simple solution in objective c worked for me for iPhone X, wouldn't be hard to convert to swift:
CGFloat bottomPadding = 0;
if (#available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.keyWindow;
bottomPadding = window.safeAreaInsets.bottom;
}
[UITabBar.appearance setSelectionIndicatorImage:[UIImage imageWithColor:[UIColor lightGrayColor]
andBounds:CGRectMake(0, 0, self.tabBar.frame.size.width/5, self.tabBar.frame.size.height + bottomPadding)]];
This is an adaptation on Glenn's solution above...
import UIKit
class BaseTabBarController: UITabBarController {
var tabBarBounds: CGRect? {
didSet {
guard tabBarBounds != oldValue else { return }
updateSelectionIndicatorColor(UIColor.green)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tabBarBounds = tabBar.bounds
}
func updateSelectionIndicatorColor(_ tintColor: UIColor) {
guard let tabBarItems = self.tabBar.items else { return }
let tabWidth = tabBar.bounds.width
let tabHeight = tabBar.bounds.height
let tabSize = CGSize(width: tabWidth / CGFloat(tabBarItems.count), height: tabHeight)
var selectionImage = UIImage(color: tintColor, size: tabSize)
UIGraphicsBeginImageContext(tabSize)
selectionImage?.draw(in: CGRect(x: 0, y: 0, width: tabSize.width, height: tabSize.height))
selectionImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tabBar.selectionIndicatorImage = selectionImage
}
}
public extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
}
you should take a look at this to make the tabbarbgtest.png resizable, then assign the image to selectionIndicatorImage, you can even do this in the storyboard editor.

Resources