Change colors in a CAGradientLayer after inserting - ios

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.

Related

Add Gradient Layer to UIImageView in IOS thru CAGradientLayer

I am trying to add a Gradient in IOS like I did in Android. So I can see my label on top of the UIImageView and its not hidden.
In android I did this in a drawable `
<gradient
android:angle="90"
android:endColor="#00ffffff"
android:startColor="#aa000000"
android:centerColor="#00ffffff" />
<corners android:radius="0dp" />
`
I am trying to do this in IOS Swift 4.2 and I get the following :
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
gradient.frame = CGRect(x: 0.0, y: 0.0, width: showImageView.frame.size.width, height: showImageView.frame.size.height)
showImageView.layer.addSublayer(gradient)
How do I get the Gradient to start black from the bottom and go up in 90 degrees?How do I change the opacity?Any idea?
A few thoughts:
Choose colors with alpha less than 1. Perhaps:
gradient.colors = [
UIColor.black.withAlphaComponent(0.8).cgColor,
UIColor.black.withAlphaComponent(0).cgColor
]
To have this gradient go vertically, choose start and end points that have the same x value. E.g. to cover bottom half, perhaps:
gradient.startPoint = CGPoint(x: 0.5, y: 1)
gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
Be very careful about using frame. You want the layer to reference the bounds of the image view (using the image view’s coordinate system), not the frame (which specifies where the image view is in its superview’s coordinate system). If your image view happens to be at 0, 0, you might not see a difference, but if you move the image view around at all, these will start to deviate. So, assuming you’re adding this gradient to the image view, itself, you’d use the image view’s bounds:
gradient.frame = showImageView.bounds
Be aware that the frame/bounds of the image view can sometimes change. So, we will implement layoutSubviews in our UIImageView or UITableViewCell subclass, and update the gradient’s frame there, e.g.
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}
That way it will update the gradient’s frame when the view’s layout changes.
The other solution is to define a UIView subclass, say GradientView, that renders the CAGradientLayer and define the base layerClass of that view to be a CAGradientLayer. Then add this view in between the image view and the label, define its constraints, and then you’ll have a gradient that changes dynamically as the GradientView size changes. (This layerClass approach has the advantage that it yields better animation/transitions than you’d get by just updating the frame programmatically.)
Thus, something like:
class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
var firstColor: UIColor = UIColor.black.withAlphaComponent(0.8) {
didSet { updateColors() }
}
var secondColor: UIColor = UIColor.black.withAlphaComponent(0) {
didSet { updateColors() }
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
}
private extension GradientView {
func configure() {
updateColors()
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.5)
}
func updateColors() {
gradientLayer.colors = [firstColor, secondColor].map { $0.cgColor }
}
}
If you really want your white text to pop, in addition to adding gradient over the image, you might also add a black glow/shadow to the text. It’s subtle, but really makes the text pop. Just make its shadow color the same color as the gradient.
So you can see the effect, here are four renditions of a cell, with (a) no gradient; (b) with gradient; (c) with gradient and nice gaussian blur around text; and (d) with simple shadow around text:
The nice, gaussian blur around the text is rendered with:
customLabel.layer.shadowColor = UIColor.black.cgColor
customLabel.layer.shadowRadius = 3
customLabel.layer.shadowOpacity = 1
customLabel.layer.masksToBounds = false
customLabel.layer.shouldRasterize = true
I think that third rendition (with gradient over the image, with glow around the text) is best. It’s subtle, but the text really pops. But gaussian blurs are computationally expensive, so if you find this adversely affects your performance too much, you can use the fourth option, with the simple, non-blurred shadow:
customLabel.shadowColor = .black
// perhaps also
// customLabel.shadowOffset = CGSize(width: -1, height: -1)

changing the color of a custom drawing in swift 4

So I've got a slider that changes the hue value of a UIColor. I want my custom drawing to have exactly this color.
My custom drawing class looks like this:
class CircleView: UIView {
var color = UIColor().white {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext() {
context.addArc(center: CGPoint(x: bounds.midX, y: bounds.midY), radius: bounds.height / 2, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
color.setFill()
context.fillPath()
}
}
}
In my viewController, I have a property observer that should change the color of the circle, that my custom drawing class draws:
My properties:
private var hueValue: Float = 0.0 {
didSet {
color = UIColor(hue: CGFloat(hueValue), saturation: 1, brightness: 1, alpha: 1)
backgroundView.backgroundColor = color
circle.color = color
}
}
var color = UIColor()
var circle = CircleView()
And my Action method:
#IBAction func colorSliderChanged(_ sender: UISlider) {
hueValue = sender.value
}
The Action method changes the hue value which changes the color of the backgroundView. But it does not change the color of the circle. The circle stays white.
Any ideas?
Here is a Screenshot of the app:
If you have a UIView added in your Storyboard, and you have set its Custom Class to CircleView, but you have not created an #IBOutlet connection to it, your code has no access to that view.
If you have var circle = CircleView() but you have not added that view to your view, nothing you change to circle will show up on screen.
In your storyboard, add your UIView, change its class to CircleView, and then ctrl-drag to create your #IBOutlet. Xcode will automatically set it to #IBOutlet weak var circle: CircleView!, and you can set its color (don't forget to remove the var circle = CricleView() line).

Add Facebook Shimmer on multiple UIViews

I am trying to add Facebook Shimmer on UICollectionViewCell which has multiple UIViews.
For one UIView, it's working fine with below code:
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Where backgroundView is the view in which I have all the subviews such as imageView, labelView and others.
While I am trying to add multiple views then first view is getting correct frame but other views' widths are becoming zero. I'm adding this code inside collectionView(_:cellForItemAt:).
let shimmeringView = FBShimmeringView(frame: imageView.frame)
shimmeringView.contentView = imageView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
let shimmeringView = FBShimmeringView(frame: labelView.frame)
shimmeringView.contentView = labelView
backgroundView.addSubview(shimmeringView)
shimmeringView.isShimmering = true
Can anyone tell me if it's the correct way to implement Facebook Shimmer for multiple UIViews or Where I am doing it wrong?
I believe there are many ways to implement FBShimmeringView, it's a matter of preferences. So in my case, I prefer the easiest way (according to me).
What I do in my tableViewCell that has of course multiple views such as imageView and labels, just like yours, is that I have multiple UIView gray color, placed on top of each views in my cell.
Then I only have ONE instance of FBShimmeringView added to my cell.
Here are some more details about what I practice for using FBShimmeringView.
*Take note that I use SnapKit to layout my views programmatically.
I have a property in my cell called isLoading like so, which determines if the gray colored views should be shown or now. If shown, of course turn on shimmering:
public var serviceIsLoading: Bool = false {
didSet {
_ = self.view_Placeholders.map { $0.isHidden = !self.serviceIsLoading }
self.view_Shimmering.isHidden = !self.serviceIsLoading
self.view_Shimmering.isShimmering = self.serviceIsLoading
}
}
Then I add a white view to my cell after adding all the subviews to the cell:
// Place the FBShimmeringView
// Try to add a dummy view
let dummyView = UIView()
dummyView.backgroundColor = .white
self.addSubview(dummyView)
dummyView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
Add the ShimerringView to the cell as well:
self.addSubview(self.view_Shimmering)
self.view_Shimmering.snp.makeConstraints { (make) in
make.height.width.equalToSuperview()
make.center.equalToSuperview()
}
Finally, make the dummyView as the contentView of the cell:
self.view_Shimmering.contentView = dummyView
My screen would look like this. Also remember to disable interaction in your tableView.
This looks cool to me when it shimmers, just one shimerring view.
Hope it helps!
Below extension is working fine.
extension UIView {
func startShimmeringViewAnimation() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
let gradientColorOne = UIColor(white: 0.90, alpha: 1.0).cgColor
let gradientColorTwo = UIColor(white: 0.95, alpha: 1.0).cgColor
gradientLayer.colors = [gradientColorOne, gradientColorTwo, gradientColorOne]
gradientLayer.locations = [0.0, 0.5, 1.0]
self.layer.addSublayer(gradientLayer)
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = [-1.0, -0.5, 0.0]
animation.toValue = [1.0, 1.5, 2.0]
animation.repeatCount = .infinity
animation.duration = 1.25
gradientLayer.add(animation, forKey: animation.keyPath)
}
}
In UITableViewCell class, we need to add the Shimmer for each views.
class UAShimmerCell: UITableViewCell {
#IBOutlet weak var thumbNailView: UIView!
#IBOutlet weak var label1View: UIView!
#IBOutlet weak var label2View: UIView!
override func awakeFromNib() {
super.awakeFromNib()
thumbNailView.startShimmeringViewAnimation()
label1View.startShimmeringViewAnimation()
label2View.startShimmeringViewAnimation()
}
}

Gradient Layer background

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]

Radial Gradience Colors Not Updating in UIView

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.

Resources