Apply Shadow along with the gradient colour with custom class - ios

I write the one custom class for gradient button, I want to apply shadow also for that.
I getting Gradient colour but I don't get the shadow.
Here I think I have issue with self.clipsToBounds = true
find my code with following.
class GradientButton: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.cornerRadius = self.frame.size.height / 2.0
gradientLayer.locations = [0.0, 1.0]
// top-right to bottom-left
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
// we only add the layer once, here when called from init
layer.addSublayer(gradientLayer)
self.clipsToBounds = true
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
applyShadow(shadoeclr: colorOne)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
func applyShadow(shadoeclr:UIColor){
layer.shadowColor = shadoeclr.cgColor
layer.shadowRadius = 5
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: 0, height: 0)
}
}

try to use masksToBounds instead of clipsToBounds

Related

Vertical Progress Bar with Gradient Layer

I have a vertical progress bar with an animating CAGradientLayer that shows "activity" to the user.
My problem is I can't get the animation to run top-down where the gradient line is parallel to x-axis. It currently animates left to right with the gradient line parallel to the y-axis.
I thought by adjusting the layer's startPoint and endPoint y-value it would do the trick, but the layer continues to animate from left to right.
Any guidance would be appreciated.
class ProgressBarView: UIView {
var color: UIColor = .red {
didSet { setNeedsDisplay() }
}
var gradientColor: UIColor = .white {
didSet { setNeedsDisplay() }
}
var progress: CGFloat = 0 {
didSet {
DispatchQueue.main.async { self.setNeedsDisplay() }
}
}
private let progressLayer = CALayer()
private let gradientLayer = CAGradientLayer()
private let backgroundMask = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
createAnimation()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
createAnimation()
}
override func draw(_ rect: CGRect) {
self.backgroundColor = UIColor.lightGray
gradientLayer.frame = rect
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
gradientLayer.endPoint = CGPoint(x: 0.5, y: progress)
backgroundMask.path = UIBezierPath(roundedRect: rect, cornerRadius: 8).cgPath
layer.mask = backgroundMask
let progressRect = CGRect(x: 0, y: rect.height, width: rect.width, height: -(rect.height - (rect.height * progress)))
progressLayer.frame = progressRect
progressLayer.backgroundColor = UIColor.black.cgColor
}
private func setupLayers() {
layer.addSublayer(gradientLayer)
gradientLayer.mask = progressLayer
gradientLayer.locations = [0.35, 0.5, 0.65]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
}
private func createAnimation() {
let flowAnimation = CABasicAnimation(keyPath: "locations")
flowAnimation.fromValue = [-0.3, -0.15, 0]
flowAnimation.toValue = [1, 1.15, 1.3]
flowAnimation.isRemovedOnCompletion = false
flowAnimation.repeatCount = Float.infinity
flowAnimation.duration = 1
gradientLayer.add(flowAnimation, forKey: "flowAnimation")
}
}
This should get you started...
On init:
// add the gradient layer
layer.addSublayer(gradientLayer)
// initial locations
gradientLayer.locations = [0.35, 0.5, 0.65]
// initial colors
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
// set start and end points
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
// set the mask
gradientLayer.mask = backgroundMask
Don't override draw() ... instead, in layoutSubviews():
var r = bounds
// make gradient layer progress % of height
r.size.height *= progress
// update the mask path
backgroundMask.path = UIBezierPath(roundedRect: r, cornerRadius: 8).cgPath
// update gradient layer frame
gradientLayer.frame = r
When you update the progress property, call setNeedsLayout() to update the layer frames.
Here's a modified version of your class:
class ProgressBarView: UIView {
var color: UIColor = .red {
didSet {
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
}
}
var gradientColor: UIColor = .white {
didSet {
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
}
}
var progress: CGFloat = 0 {
didSet {
// trigger layoutSubviews() to
// update the layer frames
setNeedsLayout()
}
}
private let gradientLayer = CAGradientLayer()
private let backgroundMask = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
self.backgroundColor = UIColor.lightGray
setupLayers()
createAnimation()
}
private func setupLayers() {
// add the gradient layer
layer.addSublayer(gradientLayer)
// initial locations
gradientLayer.locations = [0.35, 0.5, 0.65]
// initial colors
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
// set start and end points
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
// set the mask
gradientLayer.mask = backgroundMask
}
private func createAnimation() {
let flowAnimation = CABasicAnimation(keyPath: "locations")
flowAnimation.fromValue = [-0.3, -0.15, 0]
flowAnimation.toValue = [1, 1.15, 1.3]
flowAnimation.isRemovedOnCompletion = false
flowAnimation.repeatCount = Float.infinity
flowAnimation.duration = 1
gradientLayer.add(flowAnimation, forKey: "flowAnimation")
}
override func layoutSubviews() {
super.layoutSubviews()
var r = bounds
// make gradient layer progress % of height
r.size.height *= progress
// update the mask path
backgroundMask.path = UIBezierPath(roundedRect: r, cornerRadius: 8).cgPath
// update gradient layer frame
gradientLayer.frame = r
}
}
and an example controller. Progress will start at 5% and increment by 10% with each tap anywhere on the screen:
class ViewController: UIViewController {
let pbView = ProgressBarView()
let infoLabel = UILabel()
var progress: CGFloat = 0.05
override func viewDidLoad() {
super.viewDidLoad()
pbView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pbView)
infoLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(infoLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
pbView.centerXAnchor.constraint(equalTo: g.centerXAnchor, constant: 0.0),
pbView.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
pbView.widthAnchor.constraint(equalToConstant: 60.0),
pbView.heightAnchor.constraint(equalToConstant: 400.0),
infoLabel.topAnchor.constraint(equalTo: pbView.bottomAnchor, constant: 20.0),
infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
infoLabel.font = .systemFont(ofSize: 32.0, weight: .light)
pbView.progress = self.progress
updateInfo()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
progress += 0.1
pbView.progress = min(1.0, progress)
updateInfo()
}
func updateInfo() {
let pInt = Int(progress * 100.0)
infoLabel.text = "\(pInt)%"
}
}
("Percent Label" value can be off due to rounding.)
Edit - to clarify why it wasn't working..
So, why wasn't it working to begin with?
The original code was changing the gradient layer's .endPoint to a percentage of the height.
However, the .locations are percentages of the .endPoint - .startPoint value.
Suppose the view is 400-pts tall... if we're at 25% and we set:
.startPoint = CGPoint(x: 0.5, y: 0.0)
.endPoint = CGPoint(x: 0.5, y: 0.25)
the gradient will be calculated for 100-pts of height.
The .locations animation goes from [-0.3, -0.15, 0] to [1, 1.15, 1.3] 0 which is a total of 30% of the 100-pts (or 30-points). However, as soon as the location exceeds 1.0 it will fill out the rest of the layer's frame.
Here's how it looks as we animate through:
gradientLayer.locations = [0.10, 0.25, 0.4]
gradientLayer.locations = [0.40, 0.55, 0.7]
gradientLayer.locations = [0.60, 0.75, 0.9]
gradientLayer.locations = [0.85, 1.0, 1.15]
I've adjusted the gray "progress" layer to be only half of the width -- at full width, it covers the beginning of the gradient animation:
Setting aside any discussion of putting the code inside draw() or layoutSubviews(), you can "fix" the issue by commenting out a single line in your draw() func:
//gradientLayer.endPoint = CGPoint(x: 0.5, y: progress)
Now the actual gradient height will remain at 30% of the full height.
It wasn't clear initially what you wanted to do with the gray "progress" layer... here's a modified version using layoutSubviews() instead of draw(). One big benefit is that the entire thing will automatically resize if the view frame changes:
class ProgressBarView: UIView {
var color: UIColor = .red {
didSet {
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
}
}
var gradientColor: UIColor = .white {
didSet {
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
}
}
var progress: CGFloat = 0 {
didSet {
// trigger layoutSubviews() to
// update the layer frames
setNeedsLayout()
}
}
private let gradientLayer = CAGradientLayer()
private let progressLayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
self.backgroundColor = UIColor.lightGray
setupLayers()
createAnimation()
// give the full view rounded corners
self.layer.cornerRadius = 8
self.layer.masksToBounds = true
}
private func setupLayers() {
// initial locations
gradientLayer.locations = [0.35, 0.5, 0.65]
// initial colors
gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
// set start and end points
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
// add the gradient layer
layer.addSublayer(gradientLayer)
// add the gray "progress" layer
progressLayer.backgroundColor = UIColor.lightGray.cgColor
layer.addSublayer(progressLayer)
}
private func createAnimation() {
let flowAnimation = CABasicAnimation(keyPath: "locations")
flowAnimation.fromValue = [-0.3, -0.15, 0]
flowAnimation.toValue = [1, 1.15, 1.3]
flowAnimation.isRemovedOnCompletion = false
flowAnimation.repeatCount = Float.infinity
flowAnimation.duration = 1
gradientLayer.add(flowAnimation, forKey: "flowAnimation")
}
override func layoutSubviews() {
super.layoutSubviews()
var r = bounds
// update gradient layer frame
gradientLayer.frame = bounds
// make gray progress layer frame height % of height
r.size.height *= progress
// update the gray progress layer frame
progressLayer.frame = r
}
}

Horizontal Gradient colour Showing Half only in Swift?

I I am trying to give the Gradient color for Button Baground, I am trying with following code
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
layer.insertSublayer(gradientLayer, at: 0)
}
#IBOutlet weak var loginbtn: UIButton!
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.blue)
I am trying to access this method it will show the output like this How to we fix this,
If I add following code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.green)
}
it will show the following error.
Make sure you are setting the gradient frame after auto-layout has set the size of the button.
If you subclass the button, you can do so in layoutSubviews().
Otherwise, A good place to do this in your view controller is in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
loginbtn.setGradientBackground(colorOne: UIColor.red, colorTwo: UIColor.blue)
}
Edit
Based on the second image you posted - getting over-lapping gradients when called from viewDidLayoutSubviews()... As auto-layout "does its thing," that can be called more than once, and you are adding a new layer each time that is called.
I don't know what else you might be doing in your custom class, but give it a try like this:
class GradButton: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
gradientLayer.locations = [0.0, 1.0]
// top-right to bottom-left
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
// we only add the layer once, here when called from init
layer.addSublayer(gradientLayer)
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
class MyTest: UIViewController {
#IBOutlet var loginbtn: GradButton!
override func viewDidLoad() {
super.viewDidLoad()
loginbtn.setGradientBackground(colorOne: .red, colorTwo: .yellow)
}
}
Result:

Custom UIButton subclass

I'm trying my own custom UIButton. I subclassed the UIButton class and tried to add some functions this is my code:
import UIKit
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
setGradientBackground()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
private func setup() {
layer.cornerRadius = 8
}
private func setGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.blue.cgColor]
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 linked it to my IBOutlet :
#IBOutlet weak var myButton: CustomButton!
And nothing apply !
If you're adding the button in a storyboard, the initialiser that's called is init?(coder aDecoder: NSCoder), not init(frame: CGRect), so you need to add…
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
setGradientBackground()
}
Use func awakeFromNib() :
class CustomButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
setup()
setGradientBackground()
}
private func setup() {
layer.cornerRadius = 8
}
private func setGradientBackground() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.blue.cgColor]
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)
}
}

Gradient view not working when added programmatically

So my Class looks like this :
import UIKit
class GradientBarView: UIView {
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPointMake(1, 0.5)
gradient.colors = [AppColor.SecondaryGreen.CGColor, AppColor.HeritageGreen.CGColor, AppColor.HeritageGreen.CGColor]
layer.insertSublayer(gradient, atIndex: 0)
translatesAutoresizingMaskIntoConstraints = false
}
}
and this is how I use it(in my viewDidLoad)
let gradientBar = GradientBarView(frame: CGRectZero)
self.view.addSubview(gradientBar)
Then add the necessary constraints(which is fine)...so the problem is that the view is just black but if i add it via the Storyboard it works just fine
please help,what am i missing?
Thanks
Your frame is CGRectZero by the time you call gradient.frame = bounds in commonInit. Thus your bounds will also be CGRectZero. Thus the frame you are inserting has no height or width.
You can add the gradient in the draw method of your custom view. override func draw(_ rect: CGRect) Just ensure that you do not add it more than once.
Alternatively add it to layoutSubviews, depending on your use. Something like:
class GradientBarView: UIView {
var gradientLayer: CAGradientLayer?
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.removeFromSuperlayer()
gradientLayer = self.addHorizontalGradient(color: UIColor.blue)
}
}
extension UIView {
func addHorizontalGradient(color: UIColor) -> CAGradientLayer {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.startPoint = CGPoint(x:0,y:0.5)
gradient.endPoint = CGPoint(x:1,y:0.5)
gradient.colors = [color.cgColor, UIColor.white.withAlphaComponent(0).cgColor]
self.layer.insertSublayer(gradient, at: 0)
return gradient
}
}
I had the same issue. If I added a view to viewController at interface builder then gradient was displayed. But if I did it programmatically in this case gradient was absent.
This is the code that doesn't work properly.
import UIKit
class HeaderLandscape: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupFromNib()
setupView()
}
private func setupView() {
setGradientBackground()
}
private func setGradientBackground() {
let gradient = CAGradientLayer()
gradient.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: self.bounds.height)
gradient.colors = [UIColor(red: 0, green: 0, blue: 0, alpha: 0.32).cgColor,
UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor]
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
self.layer.insertSublayer(gradient, at: 0)
}
}
and moving setGradientBackground() function call from setupView() into hext method fixed the problem.
override func layoutSubviews() {
super.layoutSubviews()
setGradientBackground()
}

Adding a gradient to a tableview hides content

I have a tableview and it works well. But I have to add gradient on background. I did it with this code in viewDidLoad:
gradientLayer.frame = self.view.bounds
let color1 = UIColor.blueColor().CGColor as CGColorRef
let color2 = UIColor.redColor().CGColor as CGColorRef
gradientLayer.colors = [color1, color2]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 0)
self.view.layer.addSublayer(gradientLayer)
self.view.backgroundColor = UIColor.greenColor()
But now I don't see any content of my tableview.
For a background gradient to be appropriately configured you can use backgroundView property of UITableView and a View with CAGradientLayer. A resulting code (in Swift 3) will be:
/* A small UIView subclass displaying gradient */
class GradientView: UIView {
/* Overriding default layer class to use CAGradientLayer */
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
/* Handy accessor to avoid unnecessary casting */
private var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
/* Public properties to manipulate colors */
public var fromColor: UIColor = UIColor.red {
didSet {
var currentColors = gradientLayer.colors
currentColors![0] = fromColor.cgColor
gradientLayer.colors = currentColors
}
}
public var toColor: UIColor = UIColor.blue {
didSet {
var currentColors = gradientLayer.colors
currentColors![1] = toColor.cgColor
gradientLayer.colors = currentColors
}
}
/* Initializers overriding to have appropriately configured layer after creation */
override init(frame: CGRect) {
super.init(frame: frame)
gradientLayer.colors = [fromColor.cgColor, toColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
gradientLayer.colors = [fromColor.cgColor, toColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
}
}
/* Code that should be called in viewDidLoad method */
class MyViewController: UITableViewController {
/* ... */
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundView = GradientView()
}
/* ... */
}

Resources