Create a view with a thick inner border that has a gradient InnerGradientBorderView is a subclass of UIView that has a thick border with a gradient. I have tried to replicate the Zoom meeting UI. They apply this kind of a border during screenshare. 

import UIKit

class InnerGradientBorderView: UIView {

    var gradientLayers = [CAGradientLayer]()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        backgroundColor = UIColor.clear
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        for layer in gradientLayers {
            layer.removeFromSuperlayer()
        }

        addGradientInnerBorder(width: 8, color: UIColor.orange)
    }

    func addGradientInnerBorder(width: CGFloat, color: UIColor) {

        // Setup
        let topLeftO = CGPoint(x: 0, y: 0)
        let topLeftI = CGPoint(x: width, y: width)

        let topRightO = CGPoint(x: frame.width, y: 0)
        let topRightI = CGPoint(x: frame.width - width, y: width)

        let bottomLeftO = CGPoint(x: 0, y: frame.height)
        let bottomLeftI = CGPoint(x: width, y: frame.height - width)

        let bottomRightO = CGPoint(x: frame.width, y: frame.height)
        let bottomRightI = CGPoint(x: frame.width - width, y: frame.height - width)

        // Top
        let topPoints = [topLeftO, topLeftI, topRightI, topRightO, topLeftO]
        let topGradientPoints = [CGPoint(x: 0, y: 0), CGPoint(x: 0, y: 1)]
        gradientLayers.append(addGradientToBeizerPath(path: addClosedPathForPoints(points: topPoints), color: color, gradientPoints: topGradientPoints))

        // Left
        let leftPoints = [topLeftO, topLeftI, bottomLeftI, bottomLeftO, topLeftO]
        let leftGradientPoints = [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 0)]
        gradientLayers.append(addGradientToBeizerPath(path: addClosedPathForPoints(points: leftPoints), color: color, gradientPoints: leftGradientPoints))

        // Right
        let rightPoints = [topRightO, topRightI, bottomRightI, bottomRightO, topRightO]
        let rightGradientPoints = [CGPoint(x: 1, y: 0), CGPoint(x: 0, y: 0)]
        gradientLayers.append(addGradientToBeizerPath(path: addClosedPathForPoints(points: rightPoints), color: color, gradientPoints: rightGradientPoints))

        // Bottom
        let bottomPoints = [bottomLeftO, bottomLeftI, bottomRightI, bottomRightO, bottomLeftO]
        let bottomGradientPoints = [CGPoint(x: 0, y: 1), CGPoint(x: 0, y: 0)]
        gradientLayers.append(addGradientToBeizerPath(path: addClosedPathForPoints(points: bottomPoints), color: color, gradientPoints: bottomGradientPoints))
    }

    func addClosedPathForPoints(points: [CGPoint]) -&gt; UIBezierPath? {
        guard points.count == 5 else { return nil }

        let path = UIBezierPath()
        path.move(to: points[0])

        path.addLine(to: points[1])
        path.addLine(to: points[2])
        path.addLine(to: points[3])
        path.addLine(to: points[4])

        path.close()

        return path
    }

    func addGradientToBeizerPath(path: UIBezierPath?, color: UIColor, gradientPoints: [CGPoint]) -&gt; CAGradientLayer {
        guard let path = path, gradientPoints.count == 2 else { return CAGradientLayer() }

        let gradient = CAGradientLayer()
        gradient.frame = path.bounds
        gradient.bounds = path.bounds // This fixes the code.
        gradient.colors = [color.cgColor, UIColor.white.cgColor]
        gradient.startPoint = gradientPoints[0]
        gradient.endPoint = gradientPoints[1]

        let shapeMask = CAShapeLayer()
        shapeMask.path = path.cgPath
        gradient.mask = shapeMask

        self.layer.insertSublayer(gradient, at: 0)

        return gradient
    }
}
Nitpick: It is "Bezier", not "Beizer".



Setting the background color


  backgroundColor = UIColor.clear


can be shortened to 

backgroundColor = .clear


because the type is inferred from the context. But why do you force the
view to be fully transparent at all? Shouldn't that be left to the 
designer to configure in the interface builder? Unless there is a special
reason I would just remove that from the init methods.



Your view has three characteristic properties: the outer and inner
gradient color, and the width of the border. The values of those
properties are spread in the code: width and outer gradient color are
arguments in 

addGradientInnerBorder(width: 8, color: UIColor.orange)


and passed around, while the inner color is directly set in

gradient.colors = [color.cgColor, UIColor.white.cgColor]


I would define properties instead:

class InnerGradientBorderView: UIView {

    let borderWidth: CGFloat = 8.0
    let outerColor = UIColor.orange
    let innerColor = UIColor.white

    // ...
}


so that they are easily identified and modified. 
Even better: Use @IBInspectable and @IBDesignable

@IBDesignable
class InnerGradientBorderView: UIView {

    @IBInspectable var borderWidth: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }
    @IBInspectable var outerColor: UIColor = .orange {
        didSet {
            setNeedsLayout()
        }
    }
    @IBInspectable var innerColor: UIColor = .white {
        didSet {
            setNeedsLayout()
        }
    } 

    // ...
}


so that they can be configured directly in the interface builder,
with a live preview (compare IBInspectable / IBDesignable:





Closing a path automatically adds a line segment from the last point
to the first point of the current path segment, i.e. the final
addLine(to:) in addClosedPathForPoints() is not needed, and it
suffices to pass 4 points to the function.

Also I would not make the return value optional. Passing a wrong number
of arguments is a programming error and should be detect early in the
development instead of silently ignored:

func addClosedPathForPoints(points: [CGPoint]) -&gt; UIBezierPath {
    assert(points.count == 4)

    let path = UIBezierPath()
    path.move(to: points[0])
    path.addLine(to: points[1])
    path.addLine(to: points[2])
    path.addLine(to: points[3])
    path.close()

    return path
}




Instead of completely re-creating all gradient layers in 
layoutSubviews() it might be more efficient to create them once
(when the view is initialized), and only update the frame and mask layer
when the layout changes. 

Then your class would look like this:

@IBDesignable
class InnerGradientBorderView: UIView {

    @IBInspectable var borderWidth: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }
    @IBInspectable var outerColor: UIColor = .orange {
        didSet {
            createGradients()
            setNeedsLayout()
        }
    }
    @IBInspectable var innerColor: UIColor = .white {
        didSet {
            createGradients()
            setNeedsLayout()
        }
    } 

    var topGradient, leftGradient, rightGradient, bottomGradient: CAGradientLayer!

    override init(frame: CGRect) {
        super.init(frame: frame)
        createGradients()
    }

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

    override func layoutSubviews() {
        super.layoutSubviews()
        layoutGradients()
    }

    func createGradients() {
        topGradient = createGradient(gradientPoints: [CGPoint(x: 0, y: 0), CGPoint(x: 0, y: 1)])
        leftGradient = createGradient(gradientPoints: [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 0)])
        rightGradient = createGradient(gradientPoints: [CGPoint(x: 1, y: 0), CGPoint(x: 0, y: 0)])
        bottomGradient = createGradient(gradientPoints: [CGPoint(x: 0, y: 1), CGPoint(x: 0, y: 0)])

        layer.addSublayer(topGradient)
        layer.addSublayer(leftGradient)
        layer.addSublayer(rightGradient)
        layer.addSublayer(bottomGradient)
    }

    func createGradient(gradientPoints: [CGPoint]) -&gt; CAGradientLayer {
        let gradient = CAGradientLayer()
        gradient.colors = [outerColor.cgColor, innerColor.cgColor]
        gradient.startPoint = gradientPoints[0]
        gradient.endPoint = gradientPoints[1]
        return gradient
    }

    func layoutGradients() {
        let topLeftO = CGPoint(x: 0, y: 0)
        let topLeftI = CGPoint(x: borderWidth, y: borderWidth)

        let topRightO = CGPoint(x: frame.width, y: 0)
        let topRightI = CGPoint(x: frame.width - borderWidth, y: borderWidth)

        let bottomLeftO = CGPoint(x: 0, y: frame.height)
        let bottomLeftI = CGPoint(x: borderWidth, y: frame.height - borderWidth)

        let bottomRightO = CGPoint(x: frame.width, y: frame.height)
        let bottomRightI = CGPoint(x: frame.width - borderWidth, y: frame.height - borderWidth)

        layoutGradient(gradient: topGradient, points: [topLeftO, topLeftI, topRightI, topRightO])
        layoutGradient(gradient: leftGradient, points: [topLeftO, topLeftI, bottomLeftI, bottomLeftO])
        layoutGradient(gradient: rightGradient, points: [topRightO, topRightI, bottomRightI, bottomRightO])
        layoutGradient(gradient: bottomGradient, points: [bottomLeftO, bottomLeftI, bottomRightI, bottomRightO])
    }

    func layoutGradient(gradient: CAGradientLayer, points: [CGPoint]) {
        let path = UIBezierPath()
        path.move(to: points[0])
        path.addLine(to: points[1])
        path.addLine(to: points[2])
        path.addLine(to: points[3])
        path.close()

        gradient.frame = path.bounds
        gradient.bounds = path.bounds

        if let shapeMask = gradient.mask as? CAShapeLayer {
            // Update mask.
            shapeMask.path = path.cgPath
        } else {
            // Create and set mask.
            let shapeMask = CAShapeLayer()
            shapeMask.path = path.cgPath
            gradient.mask = shapeMask
        }
    }
}




But this is not yet perfect! The problem is that the sublayers do not
animate correctly if the view's position or size changes, e.g.
when rotating the device.
You'll notice that clearly in the iOS Simulator if you activate
"Debug -> Slow Animations":



The solution is to embed the call to layoutGradients() 
into a Core Animation transaction so that the frame and mask changes are 
synchronized with the view layer's animation, see for example


How do you animate the sublayers of the layer of a UIView during a UIView animation? on Stack Overflow.


Applied to our situation (and translated to Swift) that becomes:

    override func layoutSubviews() {
        super.layoutSubviews()

        if let animation = layer.animation(forKey: "position") {
            CATransaction.begin()
            CATransaction.setAnimationDuration(animation.duration)
            CATransaction.setAnimationTimingFunction(animation.timingFunction)

            let frameAnimation = CABasicAnimation(keyPath: "frame")
            topGradient.add(frameAnimation, forKey: "frame")
            leftGradient.add(frameAnimation, forKey: "frame")
            rightGradient.add(frameAnimation, forKey: "frame")
            bottomGradient.add(frameAnimation, forKey: "frame")

            let pathAnimation = CABasicAnimation(keyPath: "path")
            topGradient.mask?.add(pathAnimation, forKey: "path")
            leftGradient.mask?.add(pathAnimation, forKey: "path")
            rightGradient.mask?.add(pathAnimation, forKey: "path")
            bottomGradient.mask?.add(pathAnimation, forKey: "path")

            layoutGradients()
            CATransaction.commit()
        } else {
            layoutGradients()
        }
    }


And now it animates perfectly: