SnapKit and custom gradient button Swift - swift

I use SnapKit in my project and trying to add gradient on my button
I have extension:
extension UIButton {
public func setGradientColor(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: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
layer.insertSublayer(gradientLayer, at: 0)
}
}
i have button config where i am trying to use gradient:
private var playersButton: UIButton = {
let button = UIButton(type: .custom)
button.setGradientColor(colorOne: .red, colorTwo: .blue)
button.frame = button.layer.frame
return button
}()
and SnapKit here
playersButton.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(60)
make.trailing.equalToSuperview().inset(60)
make.bottom.equalTo(startGameButton).inset(100)
make.height.equalTo(screenHeight/12.82)
}
The problem is i have not result of it, i dont see gradientm but if i delete snapKit config and will use setGradient extension in viewDidLoad it works well!
Example:
private var playersButton: UIButton = {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
playersButton.setGradientColor(colorOne: .blue, colorTwo: .red)
}
How do i change my code to use first method? Thank you

The difference is that when using SnapKit you're using Auto-Layout, so your button's frame is not set when you call button.setGradientColor(colorOne: .red, colorTwo: .blue).
The result is that your gradient layer ends up with a frame size of Zero - so you don't see it.
You will likely find it much easier and more reliable to use a button subclass like this (simple example based on the code you posted):
class MyGradientButton: UIButton {
let gradLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
layer.insertSublayer(gradLayer, at: 0)
}
public func setGradientColor(colorOne: UIColor, colorTwo: UIColor) {
gradLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradLayer.locations = [0.0, 1.0]
gradLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradLayer.frame = bounds
}
}
Then change your button declaration to:
private var playersButton: MyGradientButton = {
let button = MyGradientButton()
button.setGradientColor(colorOne: .red, colorTwo: .blue)
return button
}()
Now, whether you set its frame explicitly, or if you use auto-layout (normal syntax or with SnapKit), the gradient layer will automatically adjust whenever the button's frame changes.

Related

make the uibutton rounded, shadow and gradient

can someone tell me please how to make the button rounded, shadow and gradient
here I set the gradient and shadow to the button, but without rounding:
#IBOutlet weak var info: UIButton!
info.setTitle("INFO", for: .normal)
info.setTwoGradients(colorOne: Colors.OrangeGrad, colorTwo: Colors.OrangeGradSec)
info.setTitleColor(UIColor.black, for: .normal)
info.layer.shadowColor = UIColor.darkGray.cgColor
info.layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
info.layer.shadowOpacity = 0.8
info.layer.shadowRadius = 2
view.addSubview(info)
I know that the shadow disappears because of the rounding and I found a way to fix it, ex:
final class CustomButton: UIButton {
private var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 5.0, height: 2.0)
shadowLayer.shadowOpacity = 0.7
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
}
}
but there is a line:
shadowLayer.fillColor = UIColor.white.cgColor
because of which I can't set the gradient
therefore, I cannot find a way by which all three conditions would be met
Output:
Usage
class ViewController: UIViewController {
#IBOutlet var btnGradient: CustomButton!
override func viewDidLoad() {
super.viewDidLoad()
btnGradient.gradientColors = [.red, .green]
btnGradient.setTitle("Gradient Button", for: .normal)
btnGradient.setTitleColor(.white, for: .normal)
}
}
Custom Class
Use this class as a reference to setup the attributes:
class CustomButton: UIButton {
var gradientColors : [UIColor] = [] {
didSet {
setupView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let startPoint = CGPoint(x: 0, y: 0.5)
let endPoint = CGPoint(x: 1, y: 0.5)
var btnConfig = UIButton.Configuration.plain()
btnConfig.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: layer.frame.height / 2, bottom: 5, trailing: layer.frame.height / 2)
self.configuration = btnConfig
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
//Gradient
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = gradientColors.map { $0.cgColor }
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.cornerRadius = layer.frame.height / 2
if let oldValue = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldValue, with: gradientLayer)
} else {
layer.insertSublayer(gradientLayer, below: nil)
}
//Shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.frame.height / 2).cgPath
layer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.shadowOpacity = 0.7
layer.shadowRadius = 2.0
}
}
You can use UIButton Extension or refer code
Pass colors in array with start & end point of gradient effect, you want to start & end. i.e. x=0, y=0 means TopLeft & x=1, y=1 means BottomRight
extension UIButton {
func setGradientLayer(colorsInOrder colors: [CGColor], startPoint sPoint: CGPoint = CGPoint(x: 0, y: 0.5), endPoint ePoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
let gLayer = CAGradientLayer()
gLayer.frame = self.bounds
gLayer.colors = colors
gLayer.startPoint = sPoint
gLayer.endPoint = ePoint
gLayer.cornerRadius = 5
gLayer.shadowOpacity = 0.8
gLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
layer.insertSublayer(gLayer, at: 0)
}
}

Gradient gets darker whenever leaving app and then coming back

so I am implementing a simple gradient on the lower half a UIView subclass like the following
however, every time I leave the app and reenter it, the gradient gets darker, and so forth. How do I make it so it just stays the same shade?
Here is my code for the gradient. I don't know if its correct to place it in layoutSubviews, but I couldn't get it to work just by calling the gradient code directly, such as in viewDidLoad for example:
override func layoutSubviews() {
let gradientLayer = CAGradientLayer()
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.frame = bounds
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
layer.addSublayer(gradientLayer)
}
layoutSubviews() gets called multiple times, so your gradient is getting added repeatedly.
layoutSubviews() is not really the place to be adding this gradient. The reason adding it in viewDidLoad() was not working for you is because the bounds of the view haven't been established yet. Go ahead and set up your gradient in viewDidLoad(). Keep the gradient as a property, and use viewDidLayoutSubviews() to update the frame.
class ViewController: UIViewController {
let gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
view.layer.addSublayer(gradientLayer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = view.bounds
}
}
For a view that is a subclass of UIView, you'd handle it like this:
class MyView: UIView {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
addGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addGradient()
}
func addGradient() {
gradientLayer.type = .axial
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = .init(x: 0, y: 1)
gradientLayer.zPosition = 1
gradientLayer.opacity = 0.5
layer.addSublayer(gradientLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}

Adding custom UIView to the view controller causes it to disappear in the storyboard

I created a custom UIView and then added it to a number of viewControllers. The view controller become glitchy and do not render in the storyboard. I just see the view controllers' outlines and no content. If I remove the custom view then all goes back to normal.
Can someone please take a look at my custom view code and see if I am doing anything wrong? Thank you so much.
import UIKit
#IBDesignable
class ProgressView: UIView {
#IBOutlet var contentView: UIView!
#IBInspectable var percentageProgress: CGFloat = 0.0 {
didSet {
self.setGradient()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("ProgressView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
setGradient()
}
private func setGradient() {
if let sublayers = self.contentView.layer.sublayers {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor(#colorLiteral(red: 0.3935321569, green: 0.3986310661, blue: 0.6819975972, alpha: 1)).cgColor, UIColor(#colorLiteral(red: 0.1705667078, green: 0.649338007, blue: 0.5616513491, alpha: 1)).cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.cornerRadius = contentView.frame.height / 2
contentView.layer.cornerRadius = contentView.frame.height / 2
self.contentView.layer.insertSublayer(gradientLayer, at: 0)
gradientLayer.frame = CGRect(x: contentView.frame.minX, y: contentView.frame.minY,
width: self.contentView.frame.maxX * (percentageProgress ?? 1.0), height:
contentView.frame.maxY)
self.contentView.setNeedsDisplay()
}
}

Adding multiple shadows hides the background colour and text UIButton

I was trying to add multiple shadows to my UIButton. The two shadows were added as you can see in the image. However, they hide the title and background colour of UIButton. Why is this happening? So, has the order of the layers become bottomLayer, topLayer, text and background?
The actual result
The expected Result
This is how my UIButton class looks.
class PrimaryButton: UIButton {
let cornerRadius: CGFloat = 10
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init() {
self.init(frame: .zero)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow()
}
private func configure() {
backgroundColor = .white;
layer.cornerRadius = cornerRadius
setTitle("Get Followers", for: .normal)
setTitleColor(Colours.buttonTextColour, for: .normal)
}
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
layer.addSublayer(topLayer)
layer.addSublayer(bottomLayer)
}
private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
shadowLayer.shouldRasterize = true
shadowLayer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 10).cgPath
return shadowLayer
}
}
I was able to solve the problem as follows:
Instead of adding the layers with the addSublayer(_ layer: CALayer) method, I used the insertSublayer(_ layer: CALayer, at idx: UInt32) method.
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
// Modified
layer.insertSublayer(topLayer, at: 0)
layer.insertSublayer(bottomLayer, at: 0)
}
Also, in your createShadowLayer method, instead of CALayer, I returned a CAShapeLayer.
private func createShadowLayer(color: UIColor, offset: CGSize) -> CAShapeLayer {
// Modified
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.cornerRadius).cgPath
shadowLayer.fillColor = self.backgroundColor?.cgColor
shadowLayer.shadowPath = shadowLayer.path
// shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
// shadowLayer.shouldRasterize = true
return shadowLayer
}
I don't know if either change is essential. But that's how it worked for me :)
Hello below code will help you, I got exact result what you want in button shadow
just replace some function with my code,
Your code:
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow()
}
Replace it with my code:
override func layoutSubviews() {
super.layoutSubviews()
addDropShadow(color: UIColor.red, offset: CGSize(width: -6, height: -6), btnLayer: self.layer)
addDropShadow(color: UIColor.blue, offset: CGSize(width: 6, height: 6), btnLayer: self.layer)
}
Your code:
private func addDropShadow() {
let topLayer = createShadowLayer(color: Colours.shadowWhite, offset: CGSize(width: -6, height: -6))
let bottomLayer = createShadowLayer(color: Colours.shadowBlack, offset: CGSize(width: 6, height: 6))
layer.addSublayer(topLayer)
layer.addSublayer(bottomLayer)
}
private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
shadowLayer.shouldRasterize = true
shadowLayer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 10).cgPath
return shadowLayer
}
Replace it with below code:
private func addDropShadow(color: UIColor, offset: CGSize, btnLayer : CALayer)
{
btnLayer.masksToBounds = false
btnLayer.shadowColor = color.cgColor
btnLayer.shadowOpacity = 1
btnLayer.shadowOffset = offset
btnLayer.shadowRadius = 10
}
no need to you private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer
you can remove that function.
and make sure your button type is custom

Gradient Layer not showing on UIButton

I am trying to create a custom UIButton that i can use throughout my application. I would like to apply a gradient as its background however the gradient is not showing up using this very simple Code.
It would be very helpful if someone could point out my mistake in the code below.
class GradientBtn: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = CGSize.zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1.0
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//rounded corners
layer.cornerRadius = frame.size.height / 2
//gradient
gradientLayer.locations = [0.0, 1.0]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.frame = bounds
layer.insertSublayer(gradientLayer, at: 0)
}
}
Replace
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
with
gradientLayer.colors = [Theme.colorlightBlue.cgColor, Theme.colorMidBlue.cgColor]
And it's always good to add
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = self.bounds
}
Sh_Khan is right about the CGColor references, but a few additional refinements:
Rather than adding the gradient as a sublayer, make it the layerClass of the UIButton. This makes animations much better (e.g. if the button changes size on device rotation).
When you do this, remove all attempts to set the frame of the gradientLayer.
In layoutSubviews, you’ll no longer need to update the gradientLayer.frame, so remove that entirely, but you do want to put your corner rounding there.
It’s extremely unlikely to ever matter, but when you set layer.cornerRadius (or any property of the view, its layer, or that layer's sublayers), you should never refer to self.frame. The frame of the view is the CGRect in the superview’s coordinate system. Only refer to the view’s bounds within the subclass; never its frame.
Thus:
class GradientBtn: UIButton {
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = .zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//gradient
gradientLayer.locations = [0, 1]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue].map { $0.cgColor }
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.height, bounds.width) / 2
}
}