Your impression is correct. The UIBarButtonItem
is not a view but rather instructions on how to create a view in the end.
One way you can solve this is by defining bar button item by adding it your custom view using UIBarButtonItem(customView: )
. This way you can create any view and have reference to it. Then change the view color instead of bar button item color.
If this is not possible and you need to really use this property navigationBar.tintColor
then you could still call the setter multiple times and doing the animation yourself.
You could for instance use timer to animate color by interpolating RGBA components of the two colors. It should look something like the following:
func animateColor(from: UIColor, to: UIColor, duration: TimeInterval, onSet: @escaping (_ interpolatedColor: UIColor) -> Void) {
func getRGBA(_ color: UIColor) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
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)
return (red, green, blue, alpha)
}
let sourceComponents = getRGBA(from)
let destinationComponents = getRGBA(to)
func interpolate(_ from: CGFloat, _ to: CGFloat, _ scale: CGFloat) -> CGFloat { from + (to-from)*scale }
let start: Date = .init()
Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { timer in
let now: Date = .init()
let elapsedTime = now.timeIntervalSince(start)
let scale = max(0.0, min(CGFloat(elapsedTime/duration), 1.0))
onSet(UIColor(red: interpolate(sourceComponents.red, destinationComponents.red, scale),
green: interpolate(sourceComponents.green, destinationComponents.green, scale),
blue: interpolate(sourceComponents.blue, destinationComponents.blue, scale),
alpha: interpolate(sourceComponents.alpha, destinationComponents.alpha, scale)))
if elapsedTime >= duration {
// Animation done. Exit
timer.invalidate()
}
}
}
And you could use it as easy as
var navigationBarTintColor: UIColor? {
didSet {
if let source = navigationController?.navigationBar.tintColor, let destination = navigationBarTintColor {
animateColor(from: source, to: destination, duration: 0.3) { color in
self.navigationController?.navigationBar.tintColor = color
}
} else {
navigationController?.navigationBar.tintColor = navigationBarTintColor
}
}
}
There are still other things to consider such as
- Invalidating the operation with timer when color changes twice quickly for instance.
- Adding support for curves (ease-in, ease-out)
- Interpolation may look nicer in HSV specter (Does not effect black-white colors)