1

I want to achieve something like this. I have searched for this. I get suggestions that I can put a gradient view behind the button with height and width more than the button. But I want exact corner radius and border color with gradients.

sample image

rmaddy
  • 314,917
  • 42
  • 532
  • 579

2 Answers2

3

You can make your own BorderedButton subclass that will:

  • Add gradient sublayer;
  • Create shape layer consisting of the path of the border; And
  • Use that shape layer as mask to the gradient layer to yield gradient border in the shape of the path.

E.g.:

@IBDesignable
class BorderedButton: UIButton {
    @IBInspectable var lineWidth:    CGFloat = 3  { didSet { setNeedsLayout() } }
    @IBInspectable var cornerRadius: CGFloat = 10 { didSet { setNeedsLayout() } }

    let borderLayer: CAGradientLayer = {
        let borderLayer = CAGradientLayer()
        borderLayer.type = .axial
        borderLayer.colors = [#colorLiteral(red: 0.6135130525, green: 0.3031745553, blue: 0.9506058097, alpha: 1).cgColor, #colorLiteral(red: 0.9306473136, green: 0.1160953864, blue: 0.8244602084, alpha: 1).cgColor]
        borderLayer.startPoint = CGPoint(x: 0, y: 1)
        borderLayer.endPoint = CGPoint(x: 1, y: 0)
        return borderLayer
    }()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        borderLayer.frame = bounds

        let mask = CAShapeLayer()
        let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
        mask.path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
        mask.lineWidth = lineWidth
        mask.fillColor = UIColor.clear.cgColor
        mask.strokeColor = UIColor.white.cgColor
        borderLayer.mask = mask
    }
}

private extension BorderedButton {
    func configure() {
        layer.addSublayer(borderLayer)
    }
}

Note that:

  • I update the frame of the gradient layer and the path that is used as the mask inside the layoutSubviews method, which ensures that the border is correctly rendered if the size changes (e.g. you’re using constraints to define the size of the view).
  • I’ve made this @IBDesignable so that you can even add this to a storyboard and you’ll see it rendered correctly.
  • I’ve made the cornerRadius and lineWidth to be @IBInspectable so that you can adjust these in IB (and because they have didSet observers that sets “needs layout”, they’ll ensure that changes are observable in the storyboard).

Anyway, that yields:

enter image description here

Rob
  • 415,655
  • 72
  • 787
  • 1,044
-1
extension CALayer {
    func addGradienBorder(colors:[UIColor],width:CGFloat = 1) {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame =  CGRect(origin: CGPointZero, size: self.bounds.size)
        gradientLayer.startPoint = CGPointMake(0.0, 0.5)
        gradientLayer.endPoint = CGPointMake(1.0, 0.5)
        gradientLayer.colors = colors.map({$0.CGColor})

        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = width
        shapeLayer.path = UIBezierPath(rect: self.bounds).CGPath
        shapeLayer.fillColor = nil
        shapeLayer.strokeColor = UIColor.blackColor().CGColor
        gradientLayer.mask = shapeLayer

        self.addSublayer(gradientLayer)
    }
Swati
  • 1,417
  • 1
  • 15
  • 35