How to add rounded corners to a UIView with gradient applied? - swift

I've written custom subclass of UIView that draws a gradient inside of it:
import UIKit
#IBDesignable
class PlayerCellView: UIView {
var startColor: UIColor = UIColor(red:0.20, green:0.75, blue:1.00, alpha:1.00)
var endColor: UIColor = UIColor(red:0.07, green:0.42, blue:1.00, alpha:1.00)
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.cgColor, endColor.cgColor]
let locations : [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locations)
context?.drawLinearGradient(gradient!, start: CGPoint.zero, end: CGPoint(x: 0, y: self.bounds.height), options: .drawsAfterEndLocation)
}
}
How would I now apply rounded edges to this view?

use this extension method:
extension UIView {
func addGradientLayer(with colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint, locations: [NSNumber] = [0.0, 1.0], frame: CGRect = CGRect.zero) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.locations = locations
gradientLayer.frame = frame
gradientLayer.cornerRadius = self.layer.cornerRadius
self.layer.insertSublayer(gradientLayer, at: 0)
}
}

You can add a rounded corner UIView with gradient using the following method:
func makeCircularGradient(){
let circularView = UIView()
self.view.addSubview(circularView)
circularView.translatesAutoresizingMaskIntoConstraints = false
circularView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
circularView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
circularView.widthAnchor.constraint(equalToConstant: 200).isActive = true
circularView.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.view.layoutIfNeeded()
let gradient = CAGradientLayer()
gradient.frame = circularView.bounds
gradient.colors = [UIColor.blue.cgColor,
UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
circularView.layer.addSublayer(gradient)
let circularPath = CGMutablePath()
circularPath.addArc(center: CGPoint.init(x: circularView.bounds.width / 2, y: circularView.bounds.height / 2), radius: circularView.bounds.width / 2, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true, transform: .identity)
let maskLayer = CAShapeLayer()
maskLayer.path = circularPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.red.cgColor
circularView.layer.mask = maskLayer
}
Make a UIView and set the constraints as you see fit
Make a CAGradientLayer with colours of your choice
Make a circular mask layer and apply to the UIView.
The result will be something like below:
Making a curved corner radius View with gradient is a little bit more difficult than the circular one. And can be done like :
func makeCurvedCornerGradient(){
let circularView = UIView()
self.view.addSubview(circularView)
circularView.translatesAutoresizingMaskIntoConstraints = false
circularView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
circularView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
circularView.widthAnchor.constraint(equalToConstant: 200).isActive = true
circularView.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.view.layoutIfNeeded()
let gradient = CAGradientLayer()
gradient.frame = circularView.bounds
gradient.colors = [UIColor.blue.cgColor,
UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
circularView.layer.addSublayer(gradient)
let circularPath = CGMutablePath()
circularPath.move(to: CGPoint.init(x: 20, y: 0))
circularPath.addLine(to: CGPoint.init(x: circularView.bounds.width - 20, y: 0))
circularPath.addQuadCurve(to: CGPoint.init(x: circularView.bounds.width, y: 20), control: CGPoint.init(x: circularView.bounds.width, y: 0))
circularPath.addLine(to: CGPoint.init(x: circularView.bounds.width, y: circularView.bounds.height - 20))
circularPath.addQuadCurve(to: CGPoint.init(x: circularView.bounds.width - 20, y: circularView.bounds.height), control: CGPoint.init(x: circularView.bounds.width, y: circularView.bounds.height))
circularPath.addLine(to: CGPoint.init(x: 20, y: circularView.bounds.height))
circularPath.addQuadCurve(to: CGPoint.init(x: 0, y: circularView.bounds.height - 20), control: CGPoint.init(x: 0, y: circularView.bounds.height))
circularPath.addLine(to: CGPoint.init(x: 0, y: 20))
circularPath.addQuadCurve(to: CGPoint.init(x: 20, y: 0), control: CGPoint.init(x: 0, y: 0))
let maskLayer = CAShapeLayer()
maskLayer.path = circularPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.red.cgColor
circularView.layer.mask = maskLayer
}
Change the values according to your need.
Output:

You can possibly use the Following Code:
func setGradientBorderWidthColor(view: UIView)
{
let gradientColor = CAGradientLayer()
gradientColor.frame = CGRect(origin: CGPoint.zero, size: view.frame.size)
gradientColor.colors = [UIColor.blue.cgColor, UIColor.green.cgColor]
let pathWithRadius = UIBezierPath(roundedRect:view.bounds, byRoundingCorners:[.topRight, .topLeft, .bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20.0, height: 20.0))
let shape = CAShapeLayer()
shape.lineWidth = 3
shape.path = pathWithRadius.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradientColor.mask = shape
view.layer.mask = shape
view.layer.addSublayer(gradientColor)
}
You can use this as:
let myView = UIView()
myView.backgroundColor = UIColor.gray
myView.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
setGradientBorderWidthColor(view: myView)
self.view.addSubview(myView)
Output is like this:
Hope this Helps!

Related

Swift how to mask shape layer to blur layer

I was making a progress circle, and I want its track path to have a blur effect, is there any way to achieve that?
This is what the original track looks like(the track path is transparent, I want it to be blurred)
And this is my attempt
let outPutViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let circleRadius: CGFloat = 60
let circleViewCenter = CGPoint(x: outPutViewFrame.width / 2 , y: outPutViewFrame.height / 2)
let circleView = UIView()
let progressWidth: CGFloat = 8
circleView.frame.size = CGSize(width: (circleRadius + progressWidth) * 2, height: (circleRadius + progressWidth) * 2)
circleView.center = circleViewCenter
let circleTrackPath = UIBezierPath(arcCenter: CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2), radius: circleRadius, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
let blur = UIBlurEffect(style: .light)
let blurEffect = UIVisualEffectView(effect: blur)
blurEffect.frame = circleView.bounds
blurEffect.mask(withPath: circleTrackPath, inverse: false)
extension UIView {
func mask(withPath path: UIBezierPath, inverse: Bool = false) {
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
}
maskLayer.path = path.cgPath
maskLayer.lineWidth = 5
maskLayer.lineCap = CAShapeLayerLineCap.round
self.layer.mask = maskLayer
}
}
Set maskLayer.fillRule to evenOdd, even when not inversed.
if inverse {
path.append(UIBezierPath(rect: self.bounds))
}
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
create the circleTrackPath by using a big circle and a smaller circle.
let circleCenter = CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2)
let circleTrackPath = UIBezierPath(ovalIn:
CGRect(origin: circleCenter, size: .zero)
.insetBy(dx: circleRadius, dy: circleRadius))
// smaller circle
circleTrackPath.append(CGRect(origin: circleCenter, size: .zero)
.insetBy(dx: circleRadius * 0.8, dy: circleRadius * 0.8))
Set circleTrackPath.usesEvenOddFillRule to true:
circleTrackPath.usesEvenOddFillRule = true
Now you have a blurred full circle. The non-blurred arc part can be implemented as another sublayer.
Here is a MCVE that you can paste into a playground:
let container = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
// change this to a view of your choice
let image = UIImageView(image: UIImage(named: "my_image"))
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light))
container.addSubview(image)
blur.frame = image.frame
container.addSubview(blur)
let outer = image.bounds.insetBy(dx: 30, dy: 30)
let path = UIBezierPath(ovalIn: outer)
path.usesEvenOddFillRule = true
path.append(UIBezierPath(ovalIn: outer.insetBy(dx: 10, dy: 10)))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = .evenOdd
blur.layer.mask = maskLayer
container // <--- playground quick look this
Using my profile pic as the background, this produces:

How to put a gradient in a background button witch contains a system image?

I have a button with a system image:
#IBOutlet weak var ampouleButton: UIButton!
in the viewdid load, i put this:
let ampouleButtonConfig = UIImage.SymbolConfiguration(
pointSize: 30,
weight: .regular,
scale: .large)
let ampouleButtonImage = UIImage(
systemName: "lightbulb",
withConfiguration: ampouleButtonConfig)
ampouleButton.backgroundColor = Theme.gold03 // i need to put here a gradient
ampouleButton.setImage(ampouleButtonImage, for: .normal)
ampouleButton.tintColor = Theme.blanc
ampouleButton.layer.cornerRadius = ampouleButton.frame.height / 2
ampouleButton.layer.shadowOpacity = 0.4
ampouleButton.layer.shadowRadius = 3
ampouleButton.layer.shadowOffset =
CGSize(width: 0, height: 5)
ampouleButton.alpha = 1
instead of the background color, i need to put a gradient.
i tested this but it didn't work:
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.ampouleButton.bounds
gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.white.cgColor]
ampouleButton.layer.insertSublayer(gradientLayer, at: 0)
Actually, the button image goes behind the layer. Try this:
extension UIButton {
func applyGradient(colors: [CGColor], radius: CGFloat = 0, startGradient: CGPoint = .init(x: 0.5, y: 0), endGradient: CGPoint = .init(x: 0.5, y: 1)) {
// check first if there is already a gradient layer to avoid adding more than one
let gradientLayer = CAGradientLayer()
gradientLayer.cornerRadius = radius
gradientLayer.colors = colors
gradientLayer.startPoint = startGradient
gradientLayer.endPoint = endGradient
gradientLayer.frame = bounds
layer.insertSublayer(gradientLayer, at: 0)
}
}
And in the viewDidLoad put:
ampouleButton.applyGradient(colors: [Theme.bleu!.cgColor, Theme.softBlue!.cgColor], radius: self.ampouleButton.frame.height / 2, startGradient: CGPoint(x: 0, y: 0.5), endGradient: CGPoint(x: 1, y: 0))
if let imgView = ampouleButton.imageView { ampouleButton.bringSubviewToFront(imgView) }
Find applyGradient function from here : https://stackoverflow.com/a/62093932/2303865

Swift make UIView border into Gradient Color

I want to create a gradient border on a UIView.
The following code can produce a solid for the view Box1.
nothing I have found allows me to change the frameColor to a gradient.
var frameColor = UIColor.red.cgColor
var leftBorder = CALayer()
leftBorder.frame = CGRect(x: 0.0, y: 0, width: 1, height: box1.frame.size.height )
leftBorder.backgroundColor = frameColor
I am using Swift 5 and Xcode 11.
Thank you.
Try with below extension
public extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
func setGradientBorder(
width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 0.5, y: 0),
endPoint: CGPoint = CGPoint(x: 0.5, y: 1)
) {
let existedBorder = gradientBorderLayer()
let border = existedBorder ?? CAGradientLayer()
border.frame = bounds
border.colors = colors.map { return $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
mask.path = UIBezierPath(roundedRect: bounds, cornerRadius: 0).cgPath
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = width
border.mask = mask
let exists = existedBorder != nil
if !exists {
layer.addSublayer(border)
}
}
func removeGradientBorder() {
self.gradientBorderLayer()?.removeFromSuperlayer()
}
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
}
}
Usage
self.borderView.setGradientBorder(width: 5.0, colors: [UIColor.red , UIColor.blue])
I hope this will reach your requirement.
https://medium.com/#kushal0409/gradient-button-3dc8ef763039
There is no off-the-shelf way to do this, but you could build it yourself.
Off the top of my head, the approach might look like this:
Create a CAGradientLayer and set it up to draw your gradient as
desired.
Create a CAShapeLayer that contains an empty rectangle that is your
frame.
Add your shape layer as the mask of your gradient layer.
Add the gradient layer to your view as a sub-layer of the view's
content layer.
Swift 5 extension (works with corner radius)
extension UIView {
func gradientBorder(colors: [UIColor], isVertical: Bool){
self.layer.masksToBounds = true
//Create gradient layer
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: self.bounds.size)
gradient.colors = colors.map({ (color) -> CGColor in
color.cgColor
})
//Set gradient direction
if(isVertical){
gradient.startPoint = CGPoint(x: 0.5, y: 0)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
}
else {
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
}
//Create shape layer
let shape = CAShapeLayer()
shape.lineWidth = 1
shape.path = UIBezierPath(roundedRect: gradient.frame.insetBy(dx: 1, dy: 1), cornerRadius: self.layer.cornerRadius).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
//Add layer to view
self.layer.addSublayer(gradient)
gradient.zPosition = 0
}
}

Create a UIView with rounded bottom edge

I want make view this Bottom roundedenter image description here
I think this is really close to what you want:
The rectView is your initial view;
You can play with the + 50 of quad curve height to adjust the curve
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
rectView.backgroundColor = .red
view.addSubview(rectView)
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: rectView.frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: rectView.frame.width, y: rectView.frame.height), controlPoint: CGPoint(x: rectView.frame.width / 2 , y: rectView.frame.height + 50 ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
rectView.layer.insertSublayer(shapeLayer, at: 0)
rectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
rectView.layer.cornerRadius = 10
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25
Maybe, even better:
You can create your view:
let rectView = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(rectView)
Extend UIView
extension UIView {
func addBottomArc(ofHeight height: CGFloat, topCornerRadius: CGFloat) {
let arcBezierPath = UIBezierPath()
arcBezierPath.move(to: CGPoint(x: 0, y: frame.height))
arcBezierPath.addQuadCurve(to: CGPoint(x: frame.width, y: frame.height), controlPoint: CGPoint(x: frame.width / 2 , y: frame.height + height ))
arcBezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = arcBezierPath.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.cornerRadius = topCornerRadius
}
}
So you can set the height and the corner radius like this:
rectView.addBottomArc(ofHeight: 50, topCornerRadius: 15)
and than add the shadow you need to your view:
rectView.layer.shadowRadius = 3
rectView.layer.shadowColor = UIColor.black.cgColor
rectView.layer.shadowOffset = CGSize(width: 0, height: 0)
rectView.layer.shadowOpacity = 0.25

Applying gradient layer over a UIBezierPath graph

This is my first time asking a question, so pardon me if it not very thorough.
Basically I am trying to apply a gradient layer on top of a UIBezierPath that is drawn as a graph.
Here is my graph:
Here is the gradient layer I am going for:
Here is what I have tried to draw my graph:
let path = quadCurvedPathWithPoints(points: points)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.position = CGPoint(x: 0, y: 0)
shapeLayer.lineWidth = 1.0
This fills the graph with a blue stroke, obviously, but now I am trying to apply the white to green gradient. The way I am doing it is not working. Here is the result:
Here is the code I am struggling with:
let startColor = UIColor.white.withAlphaComponent(0.5)
let endColor = UIColor.green
let gradient = CAGradientLayer()
gradient.colors = [startColor.cgColor, endColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = path.bounds
let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath
gradient.mask = shapeLayer
sparkLineView.layer.addSublayer(gradient)
Here's a little demo that draws a UIBezierPath path using a gradient. You can copy this into a Swift playground.
class GradientGraph: UIView {
override func draw(_ rect: CGRect) {
// Create the "graph"
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: frame.height * 0.5))
path.addLine(to: CGPoint(x: frame.width * 0.2, y: frame.height * 0.3))
path.addLine(to: CGPoint(x: frame.width * 0.4, y: frame.height * 0.8))
path.addLine(to: CGPoint(x: frame.width * 0.6, y: frame.height * 0.4))
path.addLine(to: CGPoint(x: frame.width * 0.8, y: frame.height * 0.7))
// Create the gradient
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.white.cgColor,UIColor.green.cgColor] as CFArray, locations: nil)!
// Draw the graph and apply the gradient
let ctx = UIGraphicsGetCurrentContext()!
ctx.setLineWidth(6)
ctx.saveGState()
ctx.addPath(path.cgPath)
ctx.replacePathWithStrokedPath()
ctx.clip()
ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: frame.width, y: 0), options: [])
ctx.restoreGState()
}
}
let graph = GradientGraph(frame: CGRect(x: 0, y: 0, width: 700, height: 700))