CAGradientLayer not applying to the full view in programmatically created UI - ios

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.

Related

Gradient Background on UITable View Controller

How to set Gradient Background on UITable View Controller?
If I implent the above code, the gradient colours works well but I can't able to view the UIImage view, UIButton in the screen.. I can only able to see the gradient colour.. please help...
import UIKit
class HomeTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
setGradientBackground()
}
//Mark:- Gradiant background colour
func setGradientBackground() {
let topColor = UIColor(red: 19/255, green: 163/255, blue: 203/255, alpha: 1)
let bottomColor = UIColor(red: 131/255, green: 197/255, blue: 86/255, alpha: 1)
let gradientColors: [CGColor] = [topColor.cgColor, bottomColor.cgColor]
let gradientLocations: [Float] = [0.0, 0.5]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations as [NSNumber]?
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Try setting the backgroundView of the tableView. Initialize an Uiview your gradient view, set it's bound equal to tableView, do all necessary stuff to setup your gradient, set this UIview as your tableView's background view.

How to have a Gradient Layer's size placed behind a UITextField, match the UITextField size, after re-sizing the layout?

What you will see in the first image, if you scroll down a little bit, is the initial screen of a fin app. Basically, every section you see on the screen is placed within a vertical Stack View. Then every Label and Cell Is placed in a horizontal Stack View and so on, so the app auto-resizes for any screen size. I was using Story Board to create the elements.
The last section has two blue UITextFields that have a gradient layer behind it. I created an extension UITextField Class in a separate file that has the gradient function constructor in it and then another Class that placed the gradient behind any UITextField that have that class attached like this:
extension UITextField {
func gradientBackground(firstColor: UIColor, secondColor: UIColor){
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
layer.addSublayer(gradientLayer)
}
}
class gradientToTextField: UITextField {
var once = true
override func layoutSubviews() {
super.layoutSubviews()
if once {
self.gradientBackground(firstColor: UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1), secondColor: UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1))
once = false
}
}
}
Now if you take a look at the second image on the right, the last section expands on tapping the Plus sign button. As a result, the third section(starting with Total) is hidden and a new stack view appears below within the same section. Everything resizes perfectly except the gradient layer. I have set a red background behind it which is the UITextField's Background, so the problem is highlighted. It appears that the Gradient Layer is shorter in height than the UITextField, which is its parent element.
Here's the full project on GitHub: https://github.com/silviuisidor/layerResizeProblem
How can I fix this?
tl:dr — Instead of gradient layer that is a sublayer of the text field, you use a gradient view with the text field as its subview.
More details
Instead of a gradient layer, use a gradient view. The view exists purely to host a gradient layer as its underlying layer; you arrange this by subclassing UIView and implementing layerClass to return CAGradientLayer.self.
class MyGradientView : UIView {
override class var layerClass : AnyClass { return CAGradientLayer.self }
private func config() {
let gradientLayer = self.layer as! CAGradientLayer
let firstColor = UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1)
let secondColor = UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1)
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
}
override init(frame: CGRect) {
super.init(frame:frame)
self.config()
}
required init?(coder: NSCoder) {
super.init(coder:coder)
self.config()
}
}
Now make the text field the subview of our gradient view. Pin it using autolayout to the center of the gradient view. The gradient view, with its text field subview, is what goes into the interface. When the animation takes place, it is the gradient view that is resized! And when that happens, the text field is automatically repositioned along with it.
Resizing the layer frame in layoutSubviews should solve your problem
extension UITextField {
func gradientBackground(firstColor: UIColor, secondColor: UIColor) -> CAGradientLayer{
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
layer.addSublayer(gradientLayer)
return gradientLayer
}
}
class gradientToTextField: UITextField {
var coloredLayer : CAGradientLayer! = nil
var once = true
override func layoutSubviews() {
super.layoutSubviews()
if once {
coloredLayer = self.gradientBackground(firstColor: UIColor(red: 0.30, green: 0.55, blue: 1.00, alpha: 1), secondColor: UIColor(red: 0.00, green: 0.36, blue: 1.00, alpha: 1))
once = false
}
coloredLayer.frame = self.bounds
}
}

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.

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