Apply gradient to just one corner of a UIView - ios

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()
}
}

Related

Custom UIButton's edges look pixelated after interacting with it

I've created a custom UIButton subclass with rounded corners, gradient background and shadows. Buttons look ok and antialiased immediately after starting the application, but if I press one it's edges become pixelated.
I've tried a lot of stuff like setting .allowsEdgeAntialiasing = true on button's layers or removing scale transform animation from "Highlighted" setter etc. and nothing helps at all :(
Here is my button class:
#IBDesignable class CircleTintedButton: UIButton {
#IBInspectable var cornerRadius : CGFloat = 1.0
#IBInspectable var shadowOffsetWidth: CGFloat = 0.0
#IBInspectable var shadowOffsetHeight: CGFloat = 2.0
#IBInspectable var shadowColor : UIColor = UIColor.gray
#IBInspectable var shadowOpacity: CGFloat = 0.3
#IBInspectable var startColor: UIColor = .blue {
didSet {
setNeedsLayout()
}
}
#IBInspectable var endColor: UIColor = .green {
didSet {
setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.shadowPath = shadowPath.cgPath
layer.shadowOpacity = Float(shadowOpacity)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.6)
gradientLayer.locations = [0, 1]
gradientLayer.frame = bounds
gradientLayer.cornerRadius = cornerRadius
gradientLayer.masksToBounds = true
layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)
}
override var isHighlighted: Bool {
get {
return super.isHighlighted
}
set {
let xScale : CGFloat = newValue ? 1.025 : 1.0
let yScale : CGFloat = newValue ? 1.1 : 1.0
UIView.animate(withDuration: 0.1) {
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
}
super.isHighlighted = newValue
}
}
}
Some screenshots from my test device (iPhone 7 # 12.1.2):
After app launch:
https://vinishko.party/files/ok.jpg
After I press this button:
https://vinishko.party/files/aliased.jpg
Spent the whole day already trying to fix this problem, please help me :D
Thanks.
You are adding another gradient layer every time layoutSubviews() is called. You can confirm this by adding an #IBAction for your button:
#IBAction func didTap(_ sender: Any) {
if let b = sender as? CircleTintedButton {
let n = b.layer.sublayers?.count
print("nunLayers: \(String(describing: n))")
}
}
You'll see that the sublayers count increases with each tap.
Add a gradient layer as a var / property of your custom button, and then only add it once:
#IBDesignable class CircleTintedButton: UIButton {
#IBInspectable var cornerRadius : CGFloat = 1.0
#IBInspectable var shadowOffsetWidth: CGFloat = 0.0
#IBInspectable var shadowOffsetHeight: CGFloat = 2.0
#IBInspectable var shadowColor : UIColor = UIColor.gray
#IBInspectable var shadowOpacity: CGFloat = 0.3
// add this var / property
private var gradLayer: CAGradientLayer?
#IBInspectable var startColor: UIColor = .blue {
didSet {
setNeedsLayout()
}
}
#IBInspectable var endColor: UIColor = .green {
didSet {
setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.shadowPath = shadowPath.cgPath
layer.shadowOpacity = Float(shadowOpacity)
// only create / add the gradient layer once
if gradLayer == nil {
let gradientLayer = CAGradientLayer()
gradientLayer.locations = [0, 1]
gradientLayer.masksToBounds = true
layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)
self.gradLayer = gradientLayer
}
gradLayer?.colors = [startColor.cgColor, endColor.cgColor]
gradLayer?.startPoint = CGPoint(x: 0.0, y: 0.0)
gradLayer?.endPoint = CGPoint(x: 1.0, y: 0.6)
gradLayer?.frame = bounds
gradLayer?.cornerRadius = cornerRadius
// original code
// let gradientLayer = CAGradientLayer()
// gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
// gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
// gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.6)
//
// gradientLayer.locations = [0, 1]
// gradientLayer.frame = bounds
// gradientLayer.cornerRadius = cornerRadius
// gradientLayer.masksToBounds = true
//
// layer.insertSublayer(gradientLayer, below: self.titleLabel?.layer)
}
override var isHighlighted: Bool {
get {
return super.isHighlighted
}
set {
let xScale : CGFloat = newValue ? 1.025 : 1.0
let yScale : CGFloat = newValue ? 1.1 : 1.0
UIView.animate(withDuration: 0.1) {
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
}
super.isHighlighted = newValue
}
}
}

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
}

Set UINavigationBar's gradient color using IBInspectable

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.

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
}
}

How could I set gradient fixed background on UICollectionView?

I used following code.
CAGradientLayer* collectionRadient = [CAGradientLayer layer];
collectionRadient.bounds = self.collectionView.bounds;
collectionRadient.anchorPoint = CGPointZero;
collectionRadient.colors = [NSArray arrayWithObjects:(id)[startColor CGColor],(id)[endColor CGColor], nil];
[self.collectionView.layer insertSublayer:collectionRadient atIndex:0];
But it drawn on cells included images. so cell was not shown.
I want to draw gradient background of UICollectionView under Cells and fixed it when view scrolled.
Let me know Please.
Try this... You have to assign a view to use background view.
CAGradientLayer* collectionGradient = [CAGradientLayer layer];
collectionGradient.bounds = self.view.bounds;
collectionGradient.anchorPoint = CGPointZero;
collectionGradient.colors = [NSArray arrayWithObjects:(id)[[UIColor redColor] CGColor],(id)[[UIColor greenColor] CGColor], nil];
UIView *vv = [[UIView alloc] init];
vview.backgroundView = vv;
[vview.backgroundView.layer insertSublayer:collectionGradient atIndex:0];
In Swift 3.0
I like to start with a custom class for gradients
import UIKit
#IBDesignable
class GradientView: UIView {
//set your start color
#IBInspectable var startColor: UIColor = UIColor.black { didSet { updateColors() }}
//set your end color
#IBInspectable var endColor: UIColor = UIColor.white { didSet { updateColors() }}
//you can change anchors and directions
#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()
}
}
With the custom class somewhere in the project and added to the target you just need to do as mentioned earlier and add it as a background view rather than background color or separate normal view sent to the back
Implement like so:
let gradient = GradientView()
gradient.frame = self.view.bounds
//add to your collectionView
collectionView?.addSubview(gradient)
collectionView?.sendSubview(toBack: gradient)
self.collectionView?.backgroundView = gradient
Directions
Create a view
Assign that view to the backgroundView of you collectionView
Create a gradient
Add gradient layer to collectionView backgroundView
Code Swift 4.2
private func addBackgroundGradient() {
let collectionViewBackgroundView = UIView()
let gradientLayer = CAGradientLayer()
gradientLayer.frame.size = view.frame.size
// Start and end for left to right gradient
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]
collectionView.backgroundView = collectionViewBackgroundView
collectionView.backgroundView?.layer.addSublayer(gradientLayer)
}
If you want a top to bottom gradient all you have to do is remove endPoint and startPoint. You can read more about gradients here.
Note that startPoint and endPoint are defined in the coordinate plane from (0.0, 0.0) to (1.0, 1.0)

Resources