I am trying to create a gradient background. i have followed steps on different tutorials but cannot get my background view to appear, I just get a white background.
I cannot understand what I am doing wrong or not doing. Code is below.
Help greatly appreciated.
import UIKit
extension CAGradientLayer {
func backgroundGradient() -> CAGradientLayer {
self.colors = [UIColor.green,UIColor.blue]
self.locations = [0.0, 1.0]
self.startPoint = CGPoint(x: 0.0, y: 1.0)
self.endPoint = CGPoint(x: 1.0, y: 1.0)
return self
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let gradient = CAGradientLayer().backgroundGradient()
gradient.frame = self.view.bounds
print(gradient.frame)
print(gradient.startPoint)
self.view.layer.insertSublayer(gradient, at: 0)
}
}
Gradient layers take CGColor instead of UIColor. Easy fix...
In your extension, change the self.colors line to:
self.colors = [UIColor.green.cgColor, UIColor.blue.cgColor]
Related
I want to custom color my tab bar element in my custom UITabController sub-class and it works fine when I'm doing it with:
tabBar.barTintColor = .blue (With any system or custom color)
But when I'm trying to add a gradient using my custom UIImage extension
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.masksToBounds = true
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: tabBar.bounds, colors: [Colors.tabBarTopGradient, Colors.tabBarBottomGradient]))
I having trouble with gradient not getting properly applied to the bottom part of the tab bar around the phone's safe area. What am I missing here? It looks like that:
Please try setting the gradient tint in viewWillLayoutSubviews(). Hope that helps!
I think you create the gradient based on tab bar height, what you need is to add the additional height to the tab bar height
override func viewDidLoad() {
super.viewDidLoad()
var frame = tabBar.bounds
let safeAreaHeight = safeAreaInsets.bottom
frame.size.height = frame.size.height + safeAreaHeight
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: frame, colors: [UIColor.red.cgColor, UIColor.blue.cgColor]))
}
public var safeAreaInsets: UIEdgeInsets {
guard let window: UIWindow = UIApplication.shared.windows.first else {
return .zero
}
if #available(iOS 11.0, *),
UIWindow.instancesRespond(to: #selector(getter: window.safeAreaInsets)) {
return window.safeAreaInsets
}
return .zero
}
I have a gradient background for my collection view cell and each gradient is different depending on what tag the cell is (e.g Christmas is a purple gradient, birthday is a green gradient) however when scrolling through the collection view, the gradients keep on changing cells. is there a way to fix this from happening?
this is the code I use to set the gradient to the background view. it is in cellForItemAt
cell.mainView.setGradientBackground(colours: self.getColourFromTag(tag: self.lists[indexPath.item].tag))
.setGradientBackground is an extension of UIView which just sets a gradient to the background. shown here:
func setGradientBackground(colours: [CGColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colours
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
I use the method below to get what colours are in the gradient
func getColourFromTag(tag: String) -> [CGColor] {
if tag == "Christmas" {
return [Colours.gradients.festive.start.cgColor, Colours.gradients.festive.end.cgColor]
}else if tag == "Birthday" {
return [Colours.gradients.lime.start.cgColor, Colours.gradients.lime.end.cgColor]
}else if tag == "Valentines Day" {
return [Colours.gradients.strawberry.start.cgColor, Colours.gradients.strawberry.end.cgColor]
}else if tag == "Charity" {
return [Colours.gradients.blueberry.start.cgColor, Colours.gradients.blueberry.end.cgColor]
}else if tag == "Event" {
return [Colours.gradients.fire.start.cgColor, Colours.gradients.fire.end.cgColor]
}else{
return [Colours.gradients.midnight.start.cgColor, Colours.gradients.midnight.end.cgColor]
}
}
I have tried appending each [colour] into an array, then placing that into the .setGradientBackground at the indexPath.item like so:
var colours = [[CGColor]]()
colours[indexPath.item] = self.getColourFromTag(tag: self.lists[indexPath.item].tag)
cell.mainView.setGradientBackground(colours: colours[indexPath.item])
however, this does not work since it is out of range. Does anyone have a solution? thank you.
Reading your code in setGradientBackground(), you always init a new CAGradientLayer and insert it at the lowest position in your cell. Every time setGradientBackground() gets called again, the new gradient layer will be positioned below all other gradients you already inserted.
Try to get a already existing CAGradientLayer from sublayers and set the new colors.
var gradientLayer = CAGradientLayer()
if let sublayers = layer.sublayers {
for sublayer in sublayers {
if let gLayer = sublayer as? CAGradientLayer {
gradientLayer = gLayer
break
}
}
}
gradientLayer.frame = bounds
gradientLayer.colors = colours
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
EDIT
This is a more swift like version (Swift 4.2) using compactMap() for getting the first existing gradient layer in layer.sublayers:
if let existingLayer = (layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
gradientLayer = existingLayer
}
I am attempting to use radial gradience within my app on a background UIView. My issue comes to play, where I want to update the view colors of the gradience multiple times. I have no errors with my code, but I can't seem to figure out how to get around this.
What I have tried is reloading the Input Views within the regular UIView as-well as the gradience class; remove the subview of the uiview, and adding a new view to the screen, which worked for only change of set colors; and I have looked over the internet, but can't seem to resolve this. All I want is for the UIView to update its colors based on the new color parameters I give it.
Here is my radial gradience code:
import UIKit
class RadialGradient: UIView {
var innerColor = UIColor.yellow
var outterColor = UIColor.red
override func draw(_ rect: CGRect) {
let colors = [innerColor.cgColor, outterColor.cgColor] as CFArray
let endRadius = min(frame.width, frame.height)
let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
UIGraphicsGetCurrentContext()!.drawRadialGradient(gradient!,
startCenter: center,
startRadius: 0.0,
endCenter: center,
endRadius: endRadius,
options: CGGradientDrawingOptions.drawsAfterEndLocation)
}
}
Here is where I am using it:
import UIKit
class TestIssuesVC: UIViewController {
var check : Bool = false
#IBAction func buttonClicked(_ sender: Any) {
if check == true {
backgroundsetting.removeFromSuperview()
print("Why wont you change to purple and black?????")
cheapFix(inner: UIColor.purple, outter: UIColor.black)
} else {
backgroundsetting.removeFromSuperview()
cheapFix(inner: UIColor.red, outter: UIColor.blue)
check = true
}
}
func cheapFix(inner: UIColor, outter: UIColor) {
let backgroundsetting = RadialGradient()
backgroundsetting.innerColor = inner
backgroundsetting.outterColor = outter
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
let backgroundsetting = RadialGradient()
override func viewWillAppear(_ animated: Bool) {
backgroundsetting.innerColor = UIColor.green
backgroundsetting.outterColor = UIColor.red
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
}
I see two things.
Your cheapFix method never updates the backgroundsetting property. It creates its own local variable of the same name. So you are actually adding new views over and over but each is sent to the back so you only ever see the first one. This is why nothing ever appears to change.
None of that is necessary. Simply create one RadialGradient view. When you want its colors to change, simply update its colors. That class needs to be fixed so it redraws itself when its properties are updated.
Make the following change to the two properties in your RadialGradient class:
var innerColor = UIColor.yellow {
didSet {
setNeedsDisplay()
}
}
var outterColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
Those changes will ensure the view redraws itself when its colors are updated.
I have a class function to create a CAGradient layer and insert it to a UIView which works fine.
However, I'd like to modify the gradient after the page is shown, and re-executing the same function does not work.
On the displayed page I use sliders to set new colors into two UIButton backgrounds, and call the function below. I'm expecting the gradient layer background to change when I change the slider but it doesn't. I've verified that the function is being called and the color values are valid.
How can I change the gradient as I move the sliders?
Here's the class function:
class func setGradientBG(_ firstColor: CGColor, secondColor: CGColor, vertical: Bool, thisView: UIView){
let gradientLayer = CAGradientLayer()
gradientLayer.frame = thisView.bounds
gradientLayer.colors = [firstColor, secondColor]
gradientLayer.locations = [0.0, 1.2]
if !vertical{
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
}
thisView.layer.insertSublayer(gradientLayer, at: 0)
}
And here's the function called from the sliders:
func doSlider(){
CommonFuncs.setGradientBG((self.TopColorBut.backgroundColor?.cgColor)!, secondColor: (self.BottomColorBut.backgroundColor?.cgColor)!, vertical: true, thisView: self.view)
}
You're adding another instance of CAGradientLayer every time you call setGradientBG. Do you really think that's a good idea?
It looks like your doSlider method is probably part of a custom UIViewController subclass. What you should do is store a reference to the gradient layer in the view controller, and update that existing layer. Example:
class ViewController: UIViewController {
private let gradientLayer = CAGradientLayer()
#IBOutlet var topColorButton: UIButton!
#IBOutlet var bottomColorButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.layer.insertSublayer(gradientLayer, at: 0)
self.updateGradientLayer()
}
#IBAction func doSlider() {
updateGradientLayer()
}
private func updateGradientLayer() {
gradientLayer.locations = [ 0.0, 1.2 ]
gradientLayer.startPoint = .zero
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
gradientLayer.colors = [
(topColorButton.backgroundColor ?? .clear).cgColor,
(bottomColorButton.backgroundColor ?? .clear).cgColor ]
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = view.bounds
}
}
Sublayers are listed in back to front order. So insert the new sublayer at last. Or remove old ones.
My UI calls for about 7-10 different gradients.
I have the first (default) gradient set up as followed:
let gradientLayer = CAGradientLayer()
And in my viewDidLoad:
gradientLayer.frame = self.view.bounds
gradientLayer.colors = [lightBlue, lightPurple]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.zPosition = -1
self.view.layer.addSublayer(gradientLayer)
gradientLayer is the default gradient. so let's say I want to define:
gradientVariantOne / gradientVariantTwo / gradientVariantThree (etc.)
but ALL of these new gradients will have the exact frame / locations / zPosition as gradientLayer.
How can I write my code so that these new gradients inherit those properties? This will keep the code much leaner, and easy to modify in the future.
Thank you
Actually I don't see what is gained by inheritance here. Just encapsulate the setting of the frame, locations, and zPosition into a method:
extension CAGradientLayer {
func configure(view:UIView) {
self.frame = view.bounds
self.locations = [0.0, 1.0]
self.zPosition = -1
}
}
Now you can just call configure to get the configuration of each gradient layer started. You could even add more parameters to configure (such as the colors array) and turn the whole thing into a one-liner:
extension CAGradientLayer {
func configure(view:UIView, colors:[CGColor]) {
self.frame = view.bounds
self.locations = [0.0, 1.0]
self.zPosition = -1
self.colors = colors
}
}
Thus a single call to configure configures the whole layer and you're done.
You can create a UIView extension and add your gradients by calling a method.
extension UIView {
private func prepareGradient() -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
//add all common setup here
return gradientLayer
}
func addGradientVariantOne() {
let gradientLayer = prepareGradient()
gradientLayer.colors = [lightBlue, lightPurple]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.zPosition = -1
self.layer.addSublayer(gradientLayer)
}
}
Then you can simply call:
self.view.addGradientVariantOne()
Just create a similar function for each gradient.