Set UINavigationBar's gradient color using IBInspectable - ios

So this is my custom class for UINavigationBar :
import UIKit
#IBDesignable
class GradientNavigationBar: UINavigationBar {
#IBInspectable var firstColor: UIColor = UIColor.clear {
didSet {
updateView()
}
}
#IBInspectable var secondColor: UIColor = UIColor.clear {
didSet {
updateView()
}
}
#IBInspectable var isHorizontal: Bool = true {
didSet {
updateView()
}
}
override class var layerClass: AnyClass {
get {
return CAGradientLayer.self
}
}
func updateView() {
let layer = self.layer as! CAGradientLayer
layer.colors = [firstColor, secondColor].map {$0.cgColor}
if (isHorizontal) {
layer.startPoint = CGPoint(x: 0, y: 0.5)
layer.endPoint = CGPoint (x: 1, y: 0.5)
} else {
layer.startPoint = CGPoint(x: 0.5, y: 0)
layer.endPoint = CGPoint (x: 0.5, y: 1)
}
setBackgroundImage(layer.createGradientImage(), for: UIBarMetrics.default)
}
}
CAGradientLayer extension :
import Foundation
import UIKit
extension CAGradientLayer {
convenience init(frame: CGRect, colors: [UIColor]) {
self.init()
self.frame = frame
self.colors = []
for color in colors {
self.colors?.append(color.cgColor)
}
startPoint = CGPoint(x: 0, y: 0)
endPoint = CGPoint(x: 0, y: 1)
}
func createGradientImage() -> UIImage? {
var image: UIImage? = nil
UIGraphicsBeginImageContext(bounds.size)
if let context = UIGraphicsGetCurrentContext() {
render(in: context)
image = UIGraphicsGetImageFromCurrentImageContext()
}
UIGraphicsEndImageContext()
return image
}
}
Storyboard attributes :
Output on Storyboard :
Expected output :
Note : I can easily do this using just code. What I want is using IBInspectable so that I can directly set this via IB.

Related

Incomprehensible work of the sublayer for cells in the CollectionView. Swift

So, the problem is that the gradient stroke is applied in a way that is not clear to me. At the same time, the problem is only when I do the logic of clicking on the cells as a radio button. If you leave the default (multiple choice), then there is no problem. Maybe somewhere I'm missing changing the height of the view on reloading the collection. Also, if I remove the gradient and include just a stroke, then everything works well. Who has any ideas?
I add the gradient directly in the cell.
class InvestorTypeCollectionViewCell: UICollectionViewCell {
#IBOutlet private weak var cellTitleLabel: UILabel!
#IBOutlet private weak var cellDescriptionLabel: UILabel!
#IBOutlet private weak var checkMarkImageView: UIImageView!
#IBOutlet private weak var itemContainerView: UIView!
weak var delegate: InvestorTypeCollectionViewCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
checkMarkImageView.image = UIImage(named: "uncheckedBox")
// remove sublayer
itemContainerView.layer.sublayers?.filter{ $0 is CAGradientLayer }.forEach{ $0.removeFromSuperlayer() }
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func configCellWith(item: InvestorTypeModel, indexPath row: Int, selectedIndex: Int? = nil) {
itemContainerView.backgroundColor = UIColor.LIGHT_BLUE_BACKGROUND
itemContainerView.layer.cornerRadius = 8
cellTitleLabel.text = item.itemTitle.uppercased()
cellDescriptionLabel.text = Transformer.share.strippingOutHtmlFrom(text: item.itemDescription)
if item.checkBoxSelected {
diselectUISetUp()
// delegate to change item checkbox to false
delegate?.cellDidSelectedAt(indexPath: row, withCheckbox: false)
} else {
if let selectedIndex = selectedIndex {
if row == selectedIndex {
// select this cell
selectUISetUp()
// delegate to change item checkbox propertie to true
delegate?.cellDidSelectedAt(indexPath: row, withCheckbox: true)
}
}
}
}
private func diselectUISetUp() {
checkMarkImageView.image = UIImage(named: "uncheckedBox")
itemContainerView.layer.borderWidth = 0
itemContainerView.layer.borderColor = UIColor.clear.cgColor
itemContainerView.layer.cornerRadius = 8
}
private func selectUISetUp() {
checkMarkImageView.image = UIImage(named: "checkedBox")
// add gradient
let colors = [UIColor(red: 0.30, green: 0.84, blue: 0.74, alpha: 1.00),
UIColor(red: 0.44, green: 0.35, blue: 0.92, alpha: 1.00),
UIColor(red: 0.26, green: 0.20, blue: 0.87, alpha: 0.93)]
itemContainerView.gradientBorder(width: 1, colors: colors, andRoundCornersWithRadius: 8)
}
}
this is where the gradient is configured
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0),
endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0) {
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
width: bounds.size.width + width, height: bounds.size.height + width)
border.colors = colors.map { return $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
mask.path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = width
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
When the data arrives, I change the size of the collection
func refreshData() {
DispatchQueue.main.async {
self.collectionView.reloadData()
guard let dataCount = self.viewModelPresenter?.data?.count else { return }
self.heightConstraintCV.constant = 1000 + 50
}
}
You will find it much easier to use a subclassed UIView that handles its own gradient border.
For example:
#IBDesignable
class GradientBorderView: UIView {
// turns on/off the gradient border
#IBInspectable public var selected: Bool = false { didSet { setNeedsLayout() } }
// default colors
// - can be set at run-time if desired
public var colors: [UIColor] = [UIColor(red: 0.30, green: 0.84, blue: 0.74, alpha: 1.00),
UIColor(red: 0.44, green: 0.35, blue: 0.92, alpha: 1.00),
UIColor(red: 0.26, green: 0.20, blue: 0.87, alpha: 0.93)]
{
didSet {
setNeedsLayout()
}
}
// default boder line width, corner radius, and inset-from-edges
// - can be set at run-time if desired
#IBInspectable public var bWidth: CGFloat = 1 { didSet { setNeedsLayout() } }
#IBInspectable public var cRadius: CGFloat = 8 { didSet { setNeedsLayout() } }
#IBInspectable public var inset: CGFloat = 0.5 { didSet { setNeedsLayout() } }
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
override func layoutSubviews() {
super.layoutSubviews()
let shapeLayer = CAShapeLayer()
if selected {
gLayer.colors = colors.compactMap( {$0.cgColor })
// make shapeLayer path the size of view inset by "inset"
// with rounded corners
// strokes are centered, so inset must be at least 1/2 of the border width
let mInset = max(inset, bWidth * 0.5)
shapeLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: mInset, dy: mInset), cornerRadius: cRadius).cgPath
// clear fill color
shapeLayer.fillColor = UIColor.clear.cgColor
// stroke color can be any non-clear color
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = bWidth
} else {
// we'll mask with an empty path
shapeLayer.path = UIBezierPath().cgPath
}
gLayer.mask = shapeLayer
}
}
So, use a GradientBorderView instead of a UIView:
#IBOutlet private weak var itemContainerView: GradientBorderView!
and all you have to do is set its selected property to show/hide the border:
itemContainerView.selected = true
or you could set .isHidden to show/hide it.
The "gradient border" will automatically update any time the view size changes.
The view is also marked #IBDesignable with #IBInspectable properties, so you can see how it looks while designing in Storyboard (note: selected is false by default, so you won't see anything unless you change that to true).

How can I apply CAGradientLayer to a UINavigationController background

I have a UINavigationController with prefersLargeTitles = true I'd like to apply a background colour to my nav bar, with a gradient mask so the colour essentially fades to a darker version.
I have tried to create a UIView apply the mask and render this. This however involves rendering the UIView as a UIImage and then as a UIColor so I can set the barTintColor.
This works when just rendering a UIView but when setting it on the navigation bar, the behaviour is not as I would like.
I would like that gradient to fill the entire green area.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func hideNavigationBar() {
navigationController?.setNavigationBarHidden(true, animated: false)
}
func showNavigationBar() {
navigationController?.setNavigationBarHidden(false, animated: false)
}
private func configureNavigationBar() {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.isTranslucent = false
navigationItem.title = "Home"
let v = BrandedHeader(frame: .zero)
v.frame = navigationController?.navigationBar.frame ?? .zero
navigationController?.navigationBar.barTintColor = UIColor(patternImage: v.asImage()!)
}
}
extension UIImage {
static func usingHex(_ hex: String) -> UIImage {
let color = UIColor.usingHex(hex)
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
extension UIView {
func asImage() -> UIImage? {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
guard let currentContext = UIGraphicsGetCurrentContext() else {
return nil
}
self.layer.render(in: currentContext)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
}
class BrandedHeader: UIView {
let gradient: CAGradientLayer = CAGradientLayer()
override init (frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.green
gradient.frame = frame
gradient.colors = [UIColor.clear, UIColor.black.withAlphaComponent(0.3)].map { $0.cgColor }
gradient.locations = [0.0, 0.7]
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x: 1.0, y: 0.0)
layer.insertSublayer(gradient, at: 0)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = frame
gradient.removeAllAnimations()
}
}
To answer your question, I don't think gradient.frame = frame in your BrandedHeader init is doing anything. I believe you explicitly need to set the height prop in layoutSubviews.
Something like gradient.frame = CGRect(x: 0, y: 0, width: frame.width, height: 500) - although I haven't tried that so please consider adjusting the values
I'm not sure if this approach is the best approach for what you are trying to achieve, I will comeback and update this answer once I have tried another way to achieve this.
The following code shows how to make a navigation bar with gradient colors. It's tested under Swift 5.
// ViewController.swift //
import UIKit
class ViewController: UIViewController {
// MARK: - Variables
// MARK: - IBOutlet
// MARK: - IBAction
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let titleString = "Home"
let navBar = navigationController!.navigationBar
let atext = NSMutableAttributedString(string: titleString)
let range = NSMakeRange(0, titleString.count)
atext.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], range: range)
atext.addAttributes([NSAttributedString.Key.strokeColor: UIColor.yellow], range: range)
atext.addAttributes([NSAttributedString.Key.strokeWidth: NSNumber.init(value: -1.0)], range: range)
let titleLabel: UILabel = UILabel.init(frame: CGRect(x: 50, y: 3, width: 220-100, height: 44))
titleLabel.attributedText = atext
titleLabel.textAlignment = NSTextAlignment.center
titleLabel.font = UIFont(name: "Helvetica", size: 24.0)
self.navigationItem.titleView = titleLabel
let rect = CGRect(x: 0, y: -20, width: (self.navigationController?.navigationBar.bounds.width)!, height: (self.navigationController?.navigationBar.bounds.height)! + 20)
let colors = [UIColor.blue, UIColor.purple, UIColor.red]
let points: [CGFloat] = [0.1, 0.5, 0.9]
let gradientView = GradientView(frame: rect, backColor: UIColor.white, gradientColors: colors, points: points, isVertical: true)
gradientView.backgroundColor = UIColor.white
navBar.insertSubview(gradientView, at: 1)
navBar.layer.shadowColor = UIColor.black.cgColor
navBar.layer.shadowRadius = 4.0
navBar.layer.shadowOpacity = 0.6
navBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
}
}
// GradientView.swift (subclass of UIView) //
import UIKit
class GradientView: UIView {
var backColor: UIColor
var gradientColors: [UIColor]
var points: [CGFloat]
var isVertical: Bool
init(frame: CGRect, backColor: UIColor, gradientColors: [UIColor], points: [CGFloat], isVertical: Bool) {
self.backColor = backColor
self.gradientColors = gradientColors
self.points = points
self.isVertical = isVertical
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
backColor.set()
UIRectFill(rect)
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
var colors = [CGColor]()
for i in 0..<gradientColors.count {
let color = gradientColors[i]
colors.append(color.cgColor)
}
gradient.colors = colors
var locations = [CGFloat]()
for i in 0..<points.count {
let point = points[i]
locations.append(point)
}
gradient.locations = locations as [NSNumber]
if !isVertical {
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x: 1.0, y: 0.0)
}
layer.addSublayer(gradient)
}
}
If you want to run the colors horizontally (left to right), set the isVertical switch to false.

Title not showing on UIButton Gradient View

I have a gradient class attached to my UIButton and the gradient shows fine. However, the title or text of the UIButton is not showing. It seems the gradient is covering the text. Heres my code:
#IBDesignable
class GradientView: UIButton {
#IBInspectable var topColor: UIColor = #colorLiteral(red: 1, green: 0.0318463631, blue: 0, alpha: 1) {
didSet {
self.setNeedsLayout()
}
}
#IBInspectable var bottomColor: UIColor = #colorLiteral(red: 1, green: 0.4265971184, blue: 0, alpha: 1) {
didSet {
self.setNeedsLayout()
}
}
override func layoutSubviews() {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.frame = self.bounds
gradientLayer.cornerRadius = layer.cornerRadius
self.layer.insertSublayer(gradientLayer, at: 0)
}
//Rounded Button Functions
#IBInspectable var cornerRadius : CGFloat = 3.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
override func awakeFromNib() {
self.setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setupView()
}
func setupView() {
self.layer.cornerRadius = cornerRadius
}
#IBInspectable var border : CGFloat = 1.0 {
didSet {
self.layer.borderWidth = border
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue?.cgColor
}
}
}
I don't know what the problem is. I used this code for another project and it showed fine.
Thanks
You forgot to call super.layoutSubviews() in the layoutSubviews().
override func layoutSubviews() {
super.layoutSubviews()
// your code
}

Apply gradient to just one corner of a UIView

I have a UIView where I display a video with a AVPlayer. I need to show the time of the video in one of the corners and for that I need to add a black gradient to one of the corners so the time in white is always visible.
Something like this:
My idea is to add a UIView (ViewA) on top of the UIView with the video, and add the gradient to ViewA and also the UILabel to that view...
My problem is that I have no clue how to add that gradient in just one corner that goes from UIColor.clear to UIColor.black.
Any clue, idea how to just apply the gradient to just one of the corners?
Thanks!
Add this to your UIView, you can change the parameters programmatically as well as from the storyboard.
#IBDesignable
class GradientView: UIView {
#IBInspectable var startColor: UIColor = .black { didSet { updateColors() }}
#IBInspectable var endColor: UIColor = .white { didSet { updateColors() }}
#IBInspectable var startLocation: Double = 0.05 { didSet { updateLocations() }}
#IBInspectable var endLocation: Double = 0.95 { didSet { updateLocations() }}
#IBInspectable var horizontalMode: Bool = false { didSet { updatePoints() }}
#IBInspectable var diagonalMode: Bool = false { didSet { updatePoints() }}
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
func updatePoints() {
if horizontalMode {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 1, y: 0) : CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0.5)
} else {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 0, y: 0) : CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 1, y: 1) : CGPoint(x: 0.5, y: 1)
}
}
func updateLocations() {
gradientLayer.locations = [startLocation as NSNumber, endLocation as NSNumber]
}
func updateColors() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
}
override func layoutSubviews() {
super.layoutSubviews()
updatePoints()
updateLocations()
updateColors()
}
}

Set Background Gradient on Button in Swift

I have no idea how to set the background gradient on a button (without making the background gradient an image). This is so different from Android.
Here's a class I have to define a returnable gradient scheme:
import UIKit
extension CAGradientLayer {
func backgroundGradientColor() -> CAGradientLayer {
let topColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(51/255.0), alpha: 1)
let bottomColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(255/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
I can use this to set the background of my entire view with the following:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let background = CAGradientLayer().backgroundGradientColor()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
}
//...
}
But how can I access the view of the button and insert the sublayer or something like that?
Your code works fine. You just have to remember to set the gradient's frame every time. It is better to just make the gradient category also set the frame of the view for you.
That way you don't forget and it applies fine.
import UIKit
extension UIView {
func applyGradient(colours: [UIColor]) -> CAGradientLayer {
return self.applyGradient(colours: colours, locations: nil)
}
func applyGradient(colours: [UIColor], locations: [NSNumber]?) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
return gradient
}
}
class ViewController: UIViewController {
#IBOutlet weak var btn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.btn.applyGradient(colours: [.yellow, .blue])
self.view.applyGradient(colours: [.yellow, .blue, .red], locations: [0.0, 0.5, 1.0])
}
}
Buttons are views. You apply gradients to it the same way you would apply it to any other view.
Picture Proof:
Video Proof:
https://i.imgur.com/ssDTqPu.mp4
It's this simple:
import UIKit
class ActualGradientButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let l = CAGradientLayer()
l.frame = self.bounds
l.colors = [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor]
l.startPoint = CGPoint(x: 0, y: 0.5)
l.endPoint = CGPoint(x: 1, y: 0.5)
l.cornerRadius = 16
layer.insertSublayer(l, at: 0)
return l
}()
}
Here below you can find the solution for Swift3 (and Swift4 too) and a little bit extended (orientation helper):
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint : CGPoint {
return points.startPoint
}
var endPoint : CGPoint {
return points.endPoint
}
var points : GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0,y: 1.0), CGPoint(x: 1.0,y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 1,y: 1))
case .horizontal:
return (CGPoint(x: 0.0,y: 0.5), CGPoint(x: 1.0,y: 0.5))
case .vertical:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 0.0,y: 1.0))
}
}
}
extension UIView {
func applyGradient(with colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(with colours: [UIColor], gradient orientation: GradientOrientation) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
#Zeb answer is great but just to clean it up and make it a little more swifty.
Computed read-only properties should avoid using get and returning Void is redundant:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
}
}
}
extension UIView {
func applyGradient(withColours colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
If you want a gradient background on a button, rather than adding the gradient as a sublayer and changing its frame in layoutSubviews, I would instead just specify the layerClass of the button to be a CAGradientLayer, so the main layer is a gradient:
#IBDesignable
public class GradientButton: UIButton {
public override class var layerClass: AnyClass { CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
#IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
#IBInspectable public var endColor: UIColor = .red { didSet { updateColors() } }
// expose startPoint and endPoint to IB
#IBInspectable public var startPoint: CGPoint {
get { gradientLayer.startPoint }
set { gradientLayer.startPoint = newValue }
}
#IBInspectable public var endPoint: CGPoint {
get { gradientLayer.endPoint }
set { gradientLayer.endPoint = newValue }
}
// while we're at it, let's expose a few more layer properties so we can easily adjust them in IB
#IBInspectable public var cornerRadius: CGFloat {
get { layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
#IBInspectable public var borderWidth: CGFloat {
get { layer.borderWidth }
set { layer.borderWidth = newValue }
}
#IBInspectable public var borderColor: UIColor? {
get { layer.borderColor.flatMap { UIColor(cgColor: $0) } }
set { layer.borderColor = newValue?.cgColor }
}
// init methods
public override init(frame: CGRect = .zero) {
super.init(frame: frame)
updateColors()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
updateColors()
}
}
private extension GradientButton {
func updateColors() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
}
}
By setting the layerClass, it will just make the main layer be a gradient, which automatically is adjusted to the bounds of the button for you. This has an advantage that if you animate the changing of the button size (e.g. on rotation events or whatever), the gradient will be correctly animated, too.
And, it is not necessary, but it may be convenient to make this class an #IBDesignable, so one can set its properties in IB, and it will be correctly rendered in the storyboard/NIB with no additional code in the view controller. For example, I can customize the corners, border, and gradient colors and direction in IB:
Try this is working for me ,
let button = UIButton(frame: CGRect(x: 60, y: 150, width: 200, height: 60))
button.setTitle("Email", for: .normal)
button.backgroundColor = .red
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
// Apply Gradient Color
let gradientLayer:CAGradientLayer = CAGradientLayer()
gradientLayer.frame.size = button.frame.size
gradientLayer.colors =
[UIColor.white.cgColor,UIColor.green.withAlphaComponent(1).cgColor]
//Use diffrent colors
button.layer.addSublayer(gradientLayer)
self.view.addSubview(button)
You can add starting and end point of gradient color.
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
For more details description refer CAGradientLayer doc
I have tried all of them this is my button init inside of viewdidload
let button = UIButton()
button.setTitle("Alper", for: .normal)
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
view.addSubview(button)
button.anchor(top: nil, left: nil, bottom: logo.topAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 50, width: 100)
let gradientx = CAGradientLayer()
gradientx.colors = [UIColor.blue,UIColor.red]
gradientx.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientx.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientx.frame = button.bounds
button.layer.insertSublayer(gradientx, at: 0)
anchor is an extension, so this is irrelevant gradient.
There are already many answers there I want add what I did to achieve this. I use this custom Button GradientButton
import Foundation
import UIKit
class GradientButton: UIButton {
let gradientColors : [UIColor]
let startPoint : CGPoint
let endPoint : CGPoint
required init(gradientColors: [UIColor] = [UIColor.red, UIColor.blue],
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.gradientColors = gradientColors
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let halfOfButtonHeight = layer.frame.height / 2
contentEdgeInsets = UIEdgeInsets(top: 10, left: halfOfButtonHeight, bottom: 10, right: halfOfButtonHeight)
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
backgroundColor = UIColor.clear
// setup gradient
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = gradientColors.map { $0.cgColor }
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.cornerRadius = 4
// replace gradient as needed
if let oldGradient = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldGradient, with: gradient)
} else {
layer.insertSublayer(gradient, below: nil)
}
// setup shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: halfOfButtonHeight).cgPath
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowOpacity = 0.85
layer.shadowRadius = 4.0
}
override var isHighlighted: Bool {
didSet {
let newOpacity : Float = isHighlighted ? 0.6 : 0.85
let newRadius : CGFloat = isHighlighted ? 6.0 : 4.0
let shadowOpacityAnimation = CABasicAnimation()
shadowOpacityAnimation.keyPath = "shadowOpacity"
shadowOpacityAnimation.fromValue = layer.shadowOpacity
shadowOpacityAnimation.toValue = newOpacity
shadowOpacityAnimation.duration = 0.1
let shadowRadiusAnimation = CABasicAnimation()
shadowRadiusAnimation.keyPath = "shadowRadius"
shadowRadiusAnimation.fromValue = layer.shadowRadius
shadowRadiusAnimation.toValue = newRadius
shadowRadiusAnimation.duration = 0.1
layer.add(shadowOpacityAnimation, forKey: "shadowOpacity")
layer.add(shadowRadiusAnimation, forKey: "shadowRadius")
layer.shadowOpacity = newOpacity
layer.shadowRadius = newRadius
let xScale : CGFloat = isHighlighted ? 1.025 : 1.0
let yScale : CGFloat = isHighlighted ? 1.05 : 1.0
UIView.animate(withDuration: 0.1) {
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
}
}
}
}
You can make GradientButton instance like this.
let button = GradientButton.init(gradientColors:[UIColor.black, UIColor.white], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: 1))
For Swift
extension UIViewController {
func makeGradientColor(`for` object : AnyObject , startPoint : CGPoint , endPoint : CGPoint) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [(UIColor.red.cgColor), (UIColor.yellow.cgColor)]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = CGRect(x: 0.0, y: 0.0, width: object.bounds.size.width, height: object.bounds.size.height)
return gradient
}
}
How to use ?
if let layers = btn.layer.sublayers{
for layer in layers {
if layer.isKind(of: CAGradientLayer.self) {
layer.removeFromSuperlayer()
}
}
}
let start : CGPoint = CGPoint(x: 0.0, y: 0.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)
let gradient: CAGradientLayer = self.makeGradientColor(for: cell.bgView, startPoint: start, endPoint: end)
btn.layer.insertSublayer(gradient, at: 0)
I've modified this great answer to improve the reusability of the button by adding init parameters for colors, radius, and gradient direction.
I also added updateGradientColors method as it might be useful if you want to change the gradient color at some point.
class GradientButton: UIButton {
private let colors: [UIColor]
private let cornerRadius: CGFloat
private let startPoint: CGPoint
private let endPoint: CGPoint
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
init(colors: [UIColor],
cornerRadius: CGFloat = 10,
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.colors = colors
self.cornerRadius = cornerRadius
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let gl = CAGradientLayer()
gl.frame = self.bounds
gl.colors = colors.map { $0.cgColor }
gl.startPoint = startPoint
gl.endPoint = endPoint
gl.cornerRadius = cornerRadius
layer.insertSublayer(gl, at: 0)
return gl
}()
func updateGradientColors(_ colors: [UIColor]) {
gradientLayer.colors = colors.map { $0.cgColor }
}
}
Gradient Button with corner radius, start and End Points Code is here...
extension UIView {
func applyGradient(colours: [UIColor], cornerRadius: CGFloat?, startPoint: CGPoint, endPoint: CGPoint) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
if let cornerRadius = cornerRadius {
gradient.cornerRadius = cornerRadius
}
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.colors = colours.map { $0.cgColor }
self.layer.insertSublayer(gradient, at: 0)
}
}
Usage :
self.yourButton.applyGradient(colours: [.red, .green], cornerRadius: 20, startPoint: CGPoint(x: 0, y: 0.5), endPoint: CGPoint(x: 1, y: 0.5))
class ButtonGradient : UIButton {
override func layoutSubviews() {
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPoint(x: 0, y: 0)
// layer.cornerRadius = CGFloat(frame.width / 20)
let color0 = UIColor(red:255/255, green:122/255, blue:0/255, alpha:1.0).cgColor
let color1 = UIColor(red:255/255, green:176/255, blue: 0/255, alpha:1.0).cgColor
let color2 = UIColor(red:250/255, green:98/255, blue: 44/255, alpha:1.0).cgColor
layer.locations = [0.5, 1.0]
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 0.5, y: 0.5)
layer.colors = [color2,color0,color1]
self.layer.insertSublayer(layer, at: 0)
}
}
After that directly assign "ButtonGredient" class to particular button in Storyboard.
Here, I have taken one UIView and add button in it.
#IBOutlet weak var btnCenter: UIButton!
#IBOutlet weak var viewCenter: UIView!
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.yello.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewCenter.bounds
// add the gradient layer to the views layer for rendering
viewCenter.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewCenter.mask = btnCenter
//Set corner Radius and border Width of button
btnCenter.layer.cornerRadius = btnCenter.frame.size.height / 2
btnCenter.layer.borderWidth = 5.0
There are ways to work with initial layer without making sublayers.
import UIKit
#IBDesignable class GradientButton: UIButton {
#IBInspectable var startColor: UIColor = UIColor.white
#IBInspectable var endColor: UIColor = UIColor.white
#IBInspectable var cornerRadius = CGFloat(5.0)
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
//This is an advanced gradient we do not use for now
// (layer as! CAGradientLayer).startPoint = CGPoint(x: 0, y: 0)
// (layer as! CAGradientLayer).endPoint = CGPoint(x: 1, y: 1)
// (layer as! CAGradientLayer).locations = [0,1]
// Simple gradient
(layer as! CAGradientLayer).colors = [startColor.cgColor, endColor.cgColor]
layer.cornerRadius = cornerRadius
}
}
class GradientButton: UIButton {
var gradientLayer: CAGradientLayer? {
didSet {
layer.sublayers?.filter { $0 is CAGradientLayer }.forEach { $0.removeFromSuperlayer() }
if let gradientLayer = gradientLayer {
layer.insertSublayer(gradientLayer, at: 0)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.frame = self.bounds
}
}

Resources