How to continually change gradient color background? - ios

I currently have a single gradient color background (see below), but I want to gradually change the colors to shades that I will pre-define in an array and have it loop through. How can I achieve this?
var colorTop = UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
var colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
func configureGradientBackground(colors:CGColor...){
let gradient: CAGradientLayer = CAGradientLayer()
let maxWidth = max(self.view.bounds.size.height,self.view.bounds.size.width)
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
let squareFrame = CGRect(origin: self.view.bounds.origin, size: CGSize(width: maxWidth, height: maxWidth * 0.935))
gradient.frame = squareFrame
gradient.colors = colors
view.layer.insertSublayer(gradient, at: 0)
}
override func viewDidLoad() {
super.viewDidLoad()
configureGradientBackground(colors: colorTop, colorBottom)
}

Really quite straightforward; I can't imagine what difficulty you are having. CAGradientLayer's colors property is itself animatable, so you simply animate the change of colors and, when the animation ends, proceed to the next animation of the change of colors.
In this example I alternate between just two sets of colors, yours and blue-and-green. So my state is a simple Bool. But obviously the example could be extended to any number of states, that is, any number of pairs of colors.
let colorTop = UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
var state = false
var grad : CAGradientLayer?
override func viewDidLoad() {
// configure the gradient layer, start the animation
}
func animate() {
let arr = self.state ? [colorTop,colorBottom] : [UIColor.green.cgColor,UIColor.blue.cgColor]
self.state = !self.state
let anim = CABasicAnimation(keyPath: "colors")
anim.duration = 10 // or whatever
anim.fromValue = self.grad!.colors
anim.toValue = arr
anim.delegate = self
self.grad?.add(anim, forKey: nil)
DispatchQueue.main.async {
CATransaction.setDisableActions(true)
self.grad?.colors = arr
}
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
DispatchQueue.main.async {
self.animate()
}
}

You could have a variable in your viewDidLoad that is an int that keeps track of the index of the array that contains all your colors. For example, 0 is your first color, 1 is your second and so on. Then in your infinite while loop (also in the viewDidLoad), you can write:
if tracker == 0 {
// apply first color
} else if tracker == 1{
// apply second color
} ...
Then you can increment the tracker by one at the end and then check your if your tracker is past the size of your array (for example, you only have 11 colors).
tracker += 1
if tracker > 10 {
tracker = 0
}

Related

incorrect Navigation bar background image coloring for iphone 12 mini

I'm setting the image of the navigation bar to a gradient. This works perfectly on all models except for iPhone 12 mini.
I've tried calling this on my main view controller in ViewWillAppear, viewDidAppear, and ViewDidLoad
Here's what it looks like on all other models
func setNavGradiant(){
guard let navigationController = self.navigationController else {print("❇️♊️>>>\(#file) \(#line): guard let failed<<<"); return}
let gradientLayer = CAGradientLayer()
var updatedFrame = navigationController.navigationBar.bounds
updatedFrame.size.height += UIApplication.shared.windows[0].windowScene?.statusBarManager?.statusBarFrame.height ?? 0
gradientLayer.frame = updatedFrame
gradientLayer.colors = [ #colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1).cgColor, #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1).cgColor] // start color and end color
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0) // vertical gradient start
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.navigationController?.navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor(#colorLiteral(red: 0.9579688907, green: 0.9579688907, blue: 0.9579688907, alpha: 1))]
}
You will probably have better results by subclassing UINavigationController:
class MyNavigationController: UINavigationController {
let gradient = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
gradient.frame = navigationBar.bounds
gradient.colors = [ #colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1).cgColor, #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1).cgColor] // start color and end color
gradient.startPoint = CGPoint(x: 0.5, y: 0.0) // vertical gradient start
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
if let image = getImageFrom(gradientLayer: gradient) {
navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)
}
}
func getImageFrom(gradientLayer:CAGradientLayer) -> UIImage? {
var gradientImage:UIImage?
UIGraphicsBeginImageContext(gradientLayer.frame.size)
if let context = UIGraphicsGetCurrentContext() {
gradientLayer.render(in: context)
gradientImage = UIGraphicsGetImageFromCurrentImageContext()?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .stretch)
}
UIGraphicsEndImageContext()
return gradientImage
}
}
Try calling:
DispatchQueue.main.async {
self.setNeedsStatusBarAppearanceUpdate()
}
after your setup is done. Should work without dispatching to main thread, but I had previously caught issues of things called from lifecycle methods not being on the main thread. So another thing to check is to make sure you are running the updates on the main thread.

Gradient Layer in Navigation Bar in iOS 11

After adding gradient layer in navigation bar, I don't see any right/left bar button items when i run on iOS 11. But the same code displays well on iOS 10/9..
Can anyone provide your valuable suggestions to fix this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setUpGradientNavigationBar()
}
func setUpGradientNavigationBar() {
let lightRedColor = UIColor(red: CGFloat(197/255.0), green: 47/255.0, blue: 40/255.0, alpha: 1.0).cgColor
let mediumRedColor = UIColor(red: CGFloat(176/255.0), green: 42/255.0, blue: 36/255.0, alpha: 1.0).cgColor
let darkRedColor = UIColor(red: CGFloat(106/255.0), green: 25/255.0, blue: 22/255.0, alpha: 1.0).cgColor
let colors = NSArray(objects: lightRedColor, mediumRedColor, darkRedColor)
let gradientLayer = getGradientLayerForColors(colors, location: 0.5, andFrame: self.navigationController?.navigationBar.bounds)
self.navigationController?.navigationBar.barTintColor = UIColor.black
self.navigationController?.navigationBar.layer.insertSublayer(gradientLayer, at: 1)
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: nil, action: nil)
}
func getGradientLayerForColors(_ colors: NSArray, location:CGFloat, andFrame frame:CGRect?) -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors as [AnyObject]
gradientLayer.locations = [0.0,NSNumber.init(value: Float(location))]
gradientLayer.frame = frame!
return gradientLayer
}
Swift 5
Includes status bar in gradient.
Doesn't use an image.
Uses transparency so content is visible through nav bar.
extension UINavigationBar {
func addGradient(_ toAlpha: CGFloat, _ color: UIColor) {
let gradient = CAGradientLayer()
gradient.colors = [
color.withAlphaComponent(toAlpha).cgColor,
color.withAlphaComponent(toAlpha).cgColor,
color.withAlphaComponent(0).cgColor
]
gradient.locations = [0, 0.8, 1]
var frame = bounds
frame.size.height += UIApplication.shared.statusBarFrame.size.height
frame.origin.y -= UIApplication.shared.statusBarFrame.size.height
gradient.frame = frame
layer.insertSublayer(gradient, at: 1)
}
}

Changing gradient background like in Instagram Log In

How to make changing gradient background like in Instagram Log In?
In Android you do it like this:
Is there a similar way to do it in Swift?
If you want to change continuously change view's background color with animation you can set it something like this.
var colors: [UIColor] = [.red, .blue, .green]
func startAnimation(index: Int) {
UIView.animate(withDuration: 2.5, delay: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.view.backgroundColor = self.colors[index]
}) { (finished) in
var currentIndex = index + 1
if currentIndex == self.colors.count { currentIndex = 0 }
self.startAnimation(index: currentIndex)
}
}
And call this function with index 0.
self.startAnimation(index: 0)
In Swift3
Use CAGradientLayer with Using CABasicAnimation
I create PastelView Library to make alike instgram background animation effect
repo https://github.com/cruisediary/Pastel
Colors
var colors: [UIColor] = [UIColor(red: 156/255, green: 39/255, blue: 176/255, alpha: 1.0),
UIColor(red: 255/255, green: 64/255, blue: 129/255, alpha: 1.0),
UIColor(red: 123/255, green: 31/255, blue: 162/255, alpha: 1.0),
UIColor(red: 32/255, green: 76/255, blue: 255/255, alpha: 1.0),
UIColor(red: 32/255, green: 158/255, blue: 255/255, alpha: 1.0),
UIColor(red: 90/255, green: 120/255, blue: 127/255, alpha: 1.0),
UIColor(red: 58/255, green: 255/255, blue: 217/255, alpha: 1.0)]
Set Gradient
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = currentGradientSet()
gradient.startPoint = CGPoint(x:0, y:1)
gradient.endPoint = CGPoint(x:1, y:0)
gradient.drawsAsynchronously = true
layer.insertSublayer(gradient, at: 0)
Add Animation to gradient
let animation = CABasicAnimation(keyPath: Animation.keyPath)
animation.duration = animationDuration
animation.toValue = currentGradientSet()
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.delegate = self //this is important
gradient.add(animation, forKey: Animation.key)
After end animation loop another color set looping
extension PastelView: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if flag {
gradient.colors = currentGradientSet()
animateGradient()
}
}
}
Pastel should help you :)
You can do it pretty easily using CAGradientLayer:
let grad = CAGradientLayer()
grad.colors = [
UIColor.red.cgColor, // top color
UIColor.blue.cgColor // bottom color
]
grad.locations = [
0.0, // start gradating at top of view
1.0 // end gradating at bottom of view
]
grad.frame = view.bounds
view.layer.addSublayer(grad)
You could increase the first item in locations (0.0) to start the gradating further down, or you could decrease 1.0 to complete gradation higher up. It's a bit tough to explain, drop this into a playground and mess around a bit until you get your desired effect.
Example gradient and playground from the code above:
You can do like this in SWIFT 3. You can give more colors in it.
let gradient1 = CAGradientLayer()
gradient1.frame = CGRect(origin: CGPoint.zero , size: CGSize (width: self.view.frame.size.width,height: self.view.frame.size.height))
gradient1.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
print(gradient1.frame)
view_gradient.layer.masksToBounds = true
view_gradient.layer.addSublayer(gradient1)
Hope this works for you.

Background gradient not see correctly in mode landscape

When I put the device in mode landscape, the background is set wrong. How do I fix it? I also could use to know how to put the entire application in landscape mode.
override func viewDidLoad() {
super.viewDidLoad()
//BACKGROUND FONDO
//A linear Gradient Consists of two colours: top colour and bottom colour
let topColor = UIColor(red: 15.0/255.0, green: 118.0/255.0, blue: 128.0/255.0, alpha: 1.0)
let bottomColor = UIColor(red: 84.0/255.0, green: 187.0/255.0, blue: 187.0/255.0, alpha: 1.0) 
//Add the top and bottom colours to an array and setup the location of those two.
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [CGFloat] = [0.0, 1.0]
//Create a Gradient CA layer
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, atIndex: 0)
} //FIN BACKGROUND GRADIENT
In your case gradient is a Layer, not View. It means that it will not resize or change position while the containing layer changes (e.g. during rotation). You have to change the frame of the sublayer manually during rotation.
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
createGredientBackground()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
gradientLayer.frame = view.layer.bounds
}
func createGredientBackground() {
let colorTop = UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at:0)
}

Swift : Set anchor Point on CABasicAnimation

The code you see below creates a CALayer (rectangle shape) and animates it from the left to the right when the user holds down on the screen ('longPressGestureRecognizer'). When they lift their finger, the CALayer stops animating, it gets pushed into an array, and when they hold on the screen again another CALayer is created. You can copy and paste code directly in new project:
//Global Variables
var layer: CALayer?
var holdGesture = UILongPressGestureRecognizer()
let animation = CABasicAnimation(keyPath: "bounds.size.width")
var layerHolder = [CALayer]()
var widthIndex = CGPoint(x: 0, y: 0)
var nextXOffset = CGFloat(0.0)
var checkIfFull = CGFloat()
var colorIndex : Int = 0
let barColors = [
//Red
UIColor(red: 0.969, green: 0.49, blue: 0.443, alpha: 1),
//Orange
UIColor(red: 0.984, green: 0.647, blue: 0.431, alpha: 1),
//Pink
UIColor(red: 0.894, green: 0.592, blue: 0.698, alpha: 1),
//Purple
UIColor(red: 0.851, green: 0.6, blue: 0.957, alpha: 1),
//Yellow
UIColor(red: 0.98, green: 0.875, blue: 0.455, alpha: 1),
//Green
UIColor(red: 0.49, green: 0.792, blue: 0.616, alpha: 1),
//Blue
UIColor(red: 0.553, green: 0.71, blue: 0.906, alpha: 1)]
func setUpView(){
self.view.addGestureRecognizer(holdGesture)
holdGesture.addTarget(self, action:"handleLongPress:")
}
func handleLongPress(sender : UILongPressGestureRecognizer){
if(sender.state == .Began) {
let newLayer = CALayer()
newLayer.frame = CGRect(x: nextXOffset, y: 0, width: 0, height: 10)
newLayer.backgroundColor = barColors[colorIndex % 7].CGColor
print("before \(nextXOffset)")
newLayer.anchorPoint = widthIndex
animation.fromValue = 0
animation.toValue = self.view.bounds.width * 2 - nextXOffset
animation.duration = 5
self.view.layer.addSublayer(newLayer)
print("Long Press Began")
newLayer.addAnimation(animation, forKey: "bounds.size.width")
layer = newLayer
}
else {
print("Long press ended")
if let layer = layer {
print("after \(nextXOffset)")
pauseLayer(layer)
layer.frame = layer.presentationLayer()!.frame
nextXOffset = CGRectGetMaxX(layer.frame)
layerHolder.append(layer)
colorIndex++
}
}
}
My issue is that when the new CALayer is created and animating, it grows from both sides not just growing to the right. I need them to be side by side to each other, one moving to the right where the other one left off. I didn't notice it until i had them interchange colors. After some research, i believe setting an anchor point from where the last CALayer left off is the correct approach. Ive been trying to achieve this but haven't been able to do it. Help me? Please?
UPDATE : I believe if I was to use the nextXOffset as the anchor point, I may be able to do this, but it is of type CGfloat not CGPoint. Help please!
This works, Im so dumb. Keeping the 'widthIndex' at (0,0) allows it to start where the last bar left off. The code works.

Resources