CAGradientLayer: other UIObjects not showing up - ios

When I use the CAGradientLayer on iOS to set up gradient for my viewcontroller, objects that I had previously placed using my interface builder are not showing up. When I take the gradientLayer function away, these objects show up. Also, when I add these objects programatically as a subview they show up in the iOS simulator. What's going on? Here is my gradientLayer code:
func setUpGradient(){
let topColor=UIColor(red: 255/255, green: 149/255, blue: 56/255,alpha: 1)
let bottomColor=UIColor(red: 216/255, green: 57/255, blue: 177/255,
alpha: 1)
let gradientColors: [CGColor]=[topColor.cgColor,bottomColor.cgColor]
var gradientLayer = CAGradientLayer()
gradientLayer.frame = self.view.bounds
gradientLayer.colors=gradientColors
gradientLayer.startPoint=CGPoint(x: 0, y:0)
gradientLayer.endPoint=CGPoint(x: 0, y:1)
self.view.layer.addSublayer(gradientLayer)
}

The reason is simple which is the gradient layer covers all the elements when you add them in IB by this line
self.view.layer.addSublayer(gradientLayer)
so you may insert it below them like this
self.view.layer.insertSublayer(gradientLayer,at:0)

Related

CAGradientLayer not applying to the full view in programmatically created UI

I'm working on an iOS project that's creating all its UIs programmatically. Not storyboards.
I want to add a gradient to the background of a UIViewController's UIView.
I created the following extension method.
extension UIView {
func addGradient(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.bounds = bounds
gradientLayer.colors = colors.map { $0.cgColor }
layer.insertSublayer(gradientLayer, at: 0)
}
}
And I added the gradient in the viewDidLoad of the ViewController like so.
let topColor = UIColor(red: 0.28, green: 0.521, blue: 0.696, alpha: 1)
let bottomColor = UIColor(red: 0.575, green: 0.615, blue: 0.692, alpha: 1)
view.addGradient(colors: [topColor, bottomColor])
But the gradient is not being applied to the full width and height of the screen.
I printed out the bounds of the view and it shows these values.
(0.0, 0.0, 375.0, 812.0)
So I'm not sure why the gradient is still not covering the full view.
In addGradient(colors:) method set the gradientLayer's frame instead of bounds, i.e.
func addGradient(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds //here.....
//rest of the code...
}
Also, as suggested move your code to viewDidLayoutSubviews() method,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topColor = UIColor(red: 0.28, green: 0.521, blue: 0.696, alpha: 1)
let bottomColor = UIColor(red: 0.575, green: 0.615, blue: 0.692, alpha: 1)
view.addGradient(colors: [topColor, bottomColor])
}
This is because, the gradient's frame must change everytime there are any changes in the view's layout. Example, in portrait and landscape orientations.
When we are adding view bounds to Gradient layer frame that time we have to update frame in viewDidLayoutSubviews. It works fine for me
Following worked for me
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.gradient.frame = containerView.bounds
}
containerView is the view where i am setting gradient layer.

Background color only appears on half of screen in landscape mode [duplicate]

This question already has an answer here:
Constraints not updating with device orientation change
(1 answer)
Closed 5 years ago.
I am building a sample login page. Everything is working fine but when I run the project on the device in landscape mode, the background color is covering only half part of the screen. I searched Google. I found 1 matching answer but it's not working.
Here is my code for background color:
let topColor = UIColor(red: 254/255.0, green: 81/255.0, blue: 150/255.0, alpha: 1)
let bottomColor = UIColor(red: 247/255.0, green: 112/255.0, blue: 98/255.0, alpha: 1.0)
let gradientColors: [CGColor] = [topColor.cgColor, bottomColor.cgColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations as [NSNumber]
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
And the result I'm getting:
This is a common mistake. The frame of the gradientLayer does not get updated when the view is laid out for the landscape mode. You have to do it manually.
SOLUTION
Be sure that you create gradientLayer and insert it as sublayer only once (e.g., in viewDidLoad). Keep gradientLayer as a instance property of the viewController so that you have access to it.
Then override layoutSubviews and there refresh the frame of the gradientLayer:
override func layoutSubviews() {
super.layoutSubviews()
// at this self.view has updated its layout, so now you can update gradientLayer's frame
gradientLayer.frame = self.view.bounds
}

Push gradient layer to background swift

My app has a gradient layer that I made in code. However when I display this onscreen it covers all the other components and hides my buttons, labels and images.
How do I push this layer back so its the background of the view?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//creating color gradient
let topColor = UIColor(red: 69/255, green: 140/255, blue: 68/255, alpha: 1).CGColor
let bottomColor = UIColor(red: 143/255, green: 239/255, blue: 141/255, alpha: 1).CGColor
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.view.frame
gradientLayer.colors = [topColor,bottomColor]
gradientLayer.locations = [0.1,1.0]
self.view.layer.addSublayer(gradientLayer)
}
You can insert a sublayer at a specific index.
self.view.layer.insertSublayer(gradientLayer, at: 0)
We can also add a subview below or above any other views using insertSubview
self.view.insertSubview(subview1_name, belowSubview: subview2_name)
self.view.insertSubview(subview1_name, aboveSubview: subview2_name)
You can add any of the subviews you have at any index as:
self.view.layer.insertSublayer(yourLayer, at: 0)
at also can be used as atIndex, and it means the indexPath of the view (from the view hierarchy), where you want to add your subview at.

Swift Button.setText doesn't work with gradient

I'm new in Swift and I have a problem. my button do not set the text if I try to use the gradient. here is my code:
let gradient:CAGradientLayer = CAGradientLayer()
let colorBottom = UIColor(red: 9/255.0, green: 137/255.0, blue: 133/255.0, alpha: 1.0).CGColor
let colorTop = UIColor(red: 222.0/255.0, green: 255.0/255.0, blue: 201.0/255.0, alpha: 1.0).CGColor
gradient.colors = [colorTop, colorBottom]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = LoginButton.bounds
gradient.cornerRadius = 25.0
LoginButton.layer.addSublayer(gradient)
LoginButton.setTitle("Login", forState: UIControlState.Normal)
LoginButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
self.view.addSubview(LoginButton)
Adding a sublayer works the same as adding a subview so adding the gradient layer may be covering the other layers where the text is drawn.
If you remove this line does it work?
LoginButton.layer.addSublayer(gradient)
If it does, try seeing how many layers are in LoginButton.layer.sublayers and try inserting the gradient layer behind one of those sublayers (wherever it looks the best) using one of the following methods:
-insertSublayer:above:
-insertSublayer:below:
-insertSublayer:atIndex:
This line is the problem:
LoginButton.layer.addSublayer(gradient)
Do not add any extra layers to a UIButton. It already knows how to draw itself. If you want it to have a gradient background, draw the gradient into an image and set that as the button's background image in the normal way.
Or make the button clear and put the gradient layer behind the button (though this is not as good).

CALayer not resizing with Autolayout

I have created a progress bar to be used in a tableView by creating a gradient layer. It works perfectly.
iPhone5:
In order to use the app on multiple devices, I have created the UIView in Storyboard, tagged it and added constraints.
However, when I use the app on an iPhone 6 the CALayer don't resize.
iPhone6:
I find this extremely stupid, but never mind. I have looked around and tried to understand how to solve this for months, but I have come up short. Does ANYONE know how to make CALayers resize with the UIView? Any help would be very much appreciated ! Thank you.
progressBar = cell.contentView.viewWithTag(3) as UIView!
progressBar.layer.cornerRadius = 4
progressBar.layer.masksToBounds = true
// create gradient layer
let gradient : CAGradientLayer = CAGradientLayer()
// create color array
let arrayColors: [AnyObject] = [
UIColor (red: 255/255, green: 138/255, blue: 1/255, alpha: 1).CGColor,
UIColor (red: 110/255, green: 110/255, blue: 118/255, alpha: 1).CGColor]
// set gradient frame bounds to match progressBar bounds
gradient.frame = progressBar.bounds
// set gradient's color array
gradient.colors = arrayColors
//Set progress(progressBar)
var percentageCompleted = 0.6
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [percentageCompleted, percentageCompleted]
gradient.endPoint = CGPoint(x: 1, y: 0.5)
// replace base layer with gradient layer
progressBar.layer.insertSublayer(gradient, atIndex: 0)
The default layer of a UIView does resize with its view, but sublayers don't (as you found out). One way to make this work is to create a custom view class, move the code you have in your question to it, and override layoutSublayersOfLayer where you can set the gradient layer to be the same size as the view. Because this code is now in a custom class, I also created a property percentageCompleted (instead of a local variable), and added a willSet clause so the bar's appearance is updated any time you change the percentageCompleted property.
class RDProgressView: UIView {
private let gradient : CAGradientLayer = CAGradientLayer()
var percentageCompleted: Double = 0.0 {
willSet{
gradient.locations = [newValue, newValue]
}
}
override func awakeFromNib() {
self.layer.cornerRadius = 4
self.layer.masksToBounds = true
// create color array
let arrayColors: [AnyObject] = [
UIColor (red: 255/255, green: 138/255, blue: 1/255, alpha: 1).CGColor,
UIColor (red: 110/255, green: 110/255, blue: 118/255, alpha: 1).CGColor]
// set gradient's color array
gradient.colors = arrayColors
//Set progress(progressBar)
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [percentageCompleted, percentageCompleted]
gradient.endPoint = CGPoint(x: 1, y: 0.5)
self.layer.insertSublayer(gradient, atIndex: 0)
}
override func layoutSublayersOfLayer(layer: CALayer!) {
super.layoutSublayersOfLayer(layer)
gradient.frame = self.bounds
}
}
In IB, you would change the class of your view to RDProgressView (in my example), and in cellForRowAtIndexPath, you would only need to get a reference to the view, and set its percentageCompleted property.
progressBar = cell.contentView.viewWithTag(3) as RDProgressView!
progressBar.percentageCompleted = 0.2
As an alternative to the accepted answer, you could also change the views layer class to be CAGradientLayer. The views layer will always be resized according to layout changes. You can achieve that by subclassing UIView
class GradientView: UIView {
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
}
then set the colors
if let gradientLayer = gradientView.layer as? CAGradientLayer {
gradientLayer.colors = arrayColors
}
It's less code than adding and maintaining a sublayer, but might not suit all use cases.

Resources