Improve Performance of Gradient Border using CAShapeLayer and CAGradientLayer - ios

I am using this method to apply gradient border to views. But when the view is in a cell of a tableview, the scrolling frame rate of the table view drops significally. Is there a way to improve the performance ? I tried setting the opaque , drawsAsynchronously and shouldRasterize to true as Apple is suggesting but nothing changed.
func addBorder(colors:[UIColor]? = nil,size:CGSize? = nil) {
_ = self.sublayers?.filter({$0.name == "GradientBorder"}).map({$0.removeFromSuperlayer()})
let shapeFrame = CGRect(origin: CGPointZero, size: size ?? bounds.size)
let gradientLayer = CAGradientLayer()
gradientLayer.name = "GradientBorder"
gradientLayer.frame = shapeFrame
gradientLayer.startPoint = CGPointMake(0.0, 0.5)
gradientLayer.endPoint = CGPointMake(1.0, 0.5)
gradientLayer.colors = colors == nil ? [UIColor.blueColor().CGColor,UIColor.redColor().CGColor] : colors!.map({$0.CGColor})
gradientLayer.contentsScale = UIScreen.mainScreen().scale
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 2
shapeLayer.path = UIBezierPath(roundedRect: shapeFrame, cornerRadius: self.cornerRadius).CGPath
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.blackColor().CGColor
gradientLayer.shouldRasterize = true
gradientLayer.opaque = true
gradientLayer.drawsAsynchronously = true
shapeLayer.drawsAsynchronously = true
shapeLayer.opaque = true
gradientLayer.mask = shapeLayer
self.addSublayer(gradientLayer)
}

Ok i found the solution and it was very easy. I simply added these three lines.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.layer.shouldRasterize = true
self.opaque = true
self.layer.rasterizationScale = UIScreen.mainScreen().scale
}

Related

Gradient Color Swift

Here is what I am trying to do:
The screenshot is taken from Iphone:
This is my code:
#IBOutlet weak var viewBottomBorder: UIView!
let gradient = CAGradientLayer()
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
let whiteColor = UIColor.black
gradient.colors = [whiteColor.withAlphaComponent(0.0).cgColor, whiteColor.withAlphaComponent(1.0), whiteColor.withAlphaComponent(1.0).cgColor]
gradient.locations = [NSNumber(value: 0.0),NSNumber(value: 0.2),NSNumber(value: 1.0)]
gradient.frame = viewBottomBorder.bounds
viewBottomBorder.layer.mask = gradient
Question: How to show same text in white color with gradient?
Can someone please explain to me how to solve this , i've tried to solve this issue but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
You need to start your gradient at the top, because it's darker at the top. So x should be have max alpha. Then as y increases reduce the alpha.
Try this:
let gradient = CAGradientLayer()
gradient.startPoint = CGPoint(x: 1.0, y: 0.0)
gradient.endPoint = CGPoint(x: 0.0, y: 1.0)
let whiteColor = UIColor.black
gradient.colors = [whiteColor.withAlphaComponent(1.0).cgColor, whiteColor.withAlphaComponent(1.0), whiteColor.withAlphaComponent(0.0).cgColor]
gradient.locations = [NSNumber(value: 1.0),NSNumber(value: 0.7),NSNumber(value: 0.0)]
gradient.frame = viewBottomBorder.bounds
viewBottomBorder.layer.mask = gradient
A little alternative:
func setupGradient(on view: UIView, withHeight height: CGFloat) {
// this is view that holds gradient
let gradientHolderView = UIView()
gradientHolderView.backgroundColor = .clear
gradientHolderView.translatesAutoresizingMaskIntoConstraints = false
// here you set layout programmatically (you can create this view in storyoard as well and attach gradient to it)
// make sure to position this view under your text
view.addSubview(gradientHolderView) // alternative: view.insertSubview(gradientHolderView, belowSubview: YourTextLaber)
gradientHolderView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
gradientHolderView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
gradientHolderView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
gradientHolderView.heightAnchor.constraint(equalToConstant: height).isActive = true
// this is needed to kinda refresh layout a little so later we can get bounds from view. I'm not sure exactly why but without this if you create view programmatically it is needed
gradientHolderView.setNeedsLayout()
gradientHolderView.layoutIfNeeded()
// here you create gradient as layer and add it as a sublayer on your view (modify colors as you like)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = gradientHolderView.bounds
gradientLayer.colors = [UIColor.clear.cgColor, UIColor(white: 0, alpha: 0.6).cgColor]
gradientHolderView.layer.addSublayer(gradientLayer)
}
Use above code to apply Gradient to your view
var gradientBackground : CAGradientLayer?
func applyGradientToBackground() {
if self.gradientBackground != nil {
self.gradientBackground?.removeFromSuperlayer()
}
self.gradientBackground = CAGradientLayer()
self.gradientBackground?.frame = self.your_view_name.bounds
let cor1 = UIColor.black.withAlphaComponent(1).cgColor
let cor2 = UIColor.white.cgColor
let arrayColors = [cor1, cor2]
self.gradientBackground?.colors = arrayColors
self.your_view_name.layer.insertSublayer(self.gradientBackground!, at: 0)
}

Gradient BG in UICollectionViewCell

I created a UICollectionView with cell like a credit cards and want to add gradient layer as a background of this cards. First I added background view for shadow(shadowView) and then add second view for gradient layer(bgView) and add CAGradientLayer as subLayer of bgView. But I didn't get any gradient view. This is my code:
class CardCell: BaseCell {
private var shadowView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.white
view.layer.shadowColor = UIColor.shadowColor.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 1.0)
view.layer.shadowOpacity = 0.1
view.layer.shadowRadius = 15.0
return view
}()
private var bgView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
view.backgroundColor = .clear
view.layer.addSublayer(createGradientLayer(for: view))
return view
}()
override func setup() {
super.setup()
addSubview(shadowView)
addSubview(bgView)
// setup constraints
shadowView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.9).isActive = true
shadowView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1).isActive = true
shadowView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
shadowView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
bgView.topAnchor.constraint(equalTo: shadowView.topAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: shadowView.bottomAnchor).isActive = true
bgView.leadingAnchor.constraint(equalTo: shadowView.leadingAnchor).isActive = true
bgView.trailingAnchor.constraint(equalTo: shadowView.trailingAnchor).isActive = true
}
private static func createGradientLayer(for view: UIView) -> CAGradientLayer {
let color = UIColor(red:0.95, green:0.42, blue:0.64, alpha:1.0)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = view.bounds
gradientLayer.colors = [color.cgColor, color.withAlphaComponent(0.7).cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.1, y: 1.1)
return gradientLayer
}
}
But If I change this code and remove gradient setting from bgView init and write it after constraints, everything is OK, BUT the shadow view go away:
. . .
bgView.leadingAnchor.constraint(equalTo: shadowView.leadingAnchor).isActive = true
bgView.trailingAnchor.constraint(equalTo: shadowView.trailingAnchor).isActive = true
bgView.layoutIfNeeded()
bgView.layer.addSublayer(CardCell.createGradientLayer(for: bgView))
extension UIView {
func addGradientToViewWithCornerRadiusAndShadow(radius:CGFloat,firstColor:UIColor,secondColor:UIColor,locations:[CGFloat]){
for layer in (self.layer.sublayers ?? []){
if let layer1 = layer as? CAGradientLayer{
layer1.removeFromSuperlayer()
}
}
let gradient = CAGradientLayer()
var rect = self.bounds
rect.size.width = ScreenWidth
gradient.frame = rect
gradient.colors = [firstColor.cgColor, secondColor.cgColor]
gradient.locations = locations as [NSNumber]
gradient.startPoint = CGPoint.init(x: 0, y: 0)
gradient.endPoint = CGPoint.init(x: 0, y: 1)
gradient.cornerRadius = radius
gradient.shadowRadius = 2
gradient.shadowColor = UIColor.lightGray.cgColor
gradient.shadowOffset = CGSize.init(width: 0.5, height: 0.5)
gradient.shadowOpacity = 0.5
self.layer.insertSublayer(gradient, at: 0)
}
}
try using this UIView's extension.Let me know if this helps.
I think your gradient layer add on your shadow view show your shadow view disappear..You should always add gradient layer at 0 index of layers.
bgView.layer.insertSublayer(CardCell.createGradientLayer(for: bgView), at: 0)
and Your layer creation method you need to use frame instead bounds.
private static func createGradientLayer(for view: UIView) -> CAGradientLayer {
let color = UIColor(red:0.95, green:0.42, blue:0.64, alpha:1.0)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = view.frame
gradientLayer.colors = [color.cgColor, color.withAlphaComponent(0.7).cgColor]
gradientLayer.locations = nil
return gradientLayer
}
The problem is When you add the gradient view the bgView is not layout completely. So the bound will be zero.
Solution:
private var gradient: CAGradientLayer?
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
shadowView.layer.shadowPath = UIBezierPath(rect: shadowView.bounds).cgPath
if let gradient = gradient {
gradient.frame = bgView.bounds
} else {
gradient = TestCell.createGradientLayer(for: bgView)
bgView.layer.addSublayer(gradient!)
}
}
OUTPUT:

Add gradient to CAShapeLayer

I got this CAShapeLayer drawing a line in a chart for me:
open func generateLayer(path: UIBezierPath) -> CAShapeLayer {
let lineLayer = CAShapeLayer()
lineLayer.lineJoin = lineJoin.CALayerString
lineLayer.lineCap = lineCap.CALayerString
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.lineWidth = lineWidth
lineLayer.strokeColor = lineColors.first?.cgColor ?? UIColor.white.cgColor
lineLayer.path = path.cgPath
if dashPattern != nil {
lineLayer.lineDashPattern = dashPattern as [NSNumber]?
}
if animDuration > 0 {
lineLayer.strokeEnd = 0.0
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = CFTimeInterval(animDuration)
pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
pathAnimation.fromValue = NSNumber(value: 0 as Float)
pathAnimation.toValue = NSNumber(value: 1 as Float)
pathAnimation.autoreverses = false
pathAnimation.isRemovedOnCompletion = false
pathAnimation.fillMode = kCAFillModeForwards
pathAnimation.beginTime = CACurrentMediaTime() + CFTimeInterval(animDelay)
lineLayer.add(pathAnimation, forKey: "strokeEndAnimation")
} else {
lineLayer.strokeEnd = 1
}
return lineLayer
}
Now I'd like to draw this line with a gradient instead of a single color. This is what I came up with, but unfortunately it does not draw the line for me. Without this added code (lineColors.count == 1) the line is drawn correctly with a single color.
fileprivate func show(path: UIBezierPath) {
let lineLayer = generateLayer(path: path)
layer.addSublayer(lineLayer)
if lineColors.count > 1 {
let gradientLayer = CAGradientLayer()
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.frame = self.bounds
gradientLayer.colors = lineColors
gradientLayer.mask = lineLayer
layer.addSublayer(gradientLayer)
}
}
Well turns out I was searching more than an hour for this line:
gradientLayer.colors = lineColors
I forgot to map the UIColors objects in this array to CGColorRef objects...
This line fixed it for me:
gradientLayer.colors = lineColors.map({$0.cgColor})

How To Add Border To a UIView With Mask?

Here's what I need to do to my two buttons.
and here's what I got right now.
What I'm doing:
- Set these up in IB inside a stackView.
- Add masks
- Add borders with width and color
- Add shadow.
The borders are being added but being cut-off as well by the masks.
Codes:
public func addCornerRadiusToCorners(
_ corners: UIRectCorner,
cornerRadius: CGFloat,
borderColor: UIColor,
borderWidth: CGFloat) {
// self.layer.masksToBounds = true
// self.clipsToBounds = true
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.cgColor
let size = CGSize(width: cornerRadius, height: cornerRadius)
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size)
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
public func addDefaultShadow() {
let shadowPath = UIBezierPath(rect: self.bounds)
self.layer.masksToBounds = false
self.layer.shadowOffset = CGSize(width: 1, height: 1)
self.layer.shadowOpacity = 0.5
self.layer.shadowPath = shadowPath.cgPath
}
Any idea how to achieve the result in the first photo?
EDIT: the border is being cut, the result was just got from the UI Debugger of Xcode. Sorry.
Add corner radius to you view on viewDidAppear
#IBOutlet weak var segmentView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
segmentView.layer.cornerRadius = segmentView.frame.size.height/2;
segmentView.layer.borderWidth = 1.0;
segmentView.clipsToBounds = true
segmentView.layer.borderColor = UIColor.lightGray.cgColor
segmentView.layer.shadowColor = UIColor.black.cgColor
segmentView.layer.shadowOpacity = 0.2
segmentView.layer.shadowRadius = 10.0
segmentView.layer.shadowOffset = CGSize(width: 1, height: 1)
segmentView.layer.masksToBounds = false
}
Sample Output:
you forgot to clip ur view.
self.clipsToBounds = true
you need to set maskstobounds to the view layer
segmentView.layer.masksToBounds = true

Chopped edge around border of image

I have an image with a border
let smallicon: UIImageView = {
let smallicon = UIImageView()
smallicon.layer.borderWidth = 2
smallicon.layer.borderColor = UIColor.whiteColor().CGColor
smallicon.hidden = true
return smallicon
}()
The problem is that is has tiny chopped edge around border (small image with yellow and black lines)
How to get rid of it ?
Solution
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a view with red background for demonstration
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.center = view.center
v.backgroundColor = UIColor.redColor()
view.addSubview(v)
// Add rounded corners
let maskLayer = CAShapeLayer()
maskLayer.frame = v.bounds
maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
v.layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clearColor().CGColor
borderLayer.strokeColor = UIColor.greenColor().CGColor
borderLayer.lineWidth = 5
borderLayer.frame = v.bounds
v.layer.addSublayer(borderLayer)
}
}
NOTE! in order this to work target view must have frame property setup with size. without size your view will not be seen at all

Resources