I have this custom code that i wrote awhile back in order to have a gradient border on my views. Only problem is, it has slowly become my top crash in my app.
The crashlog I am getting in crashlytics points to the line super.layoutSubviews() which doesn't make much sense to me.
Anyone have any idea on improvements, or a different approach to doing this same thing, that can lead to fewer crashes? I get 120-200 a week from this view.
class GradientBorderView: UIView {
var enableGradientBorder: Bool = false
var borderGradientColors: [UIColor] = [] {
didSet {
setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard enableGradientBorder && !borderGradientColors.isEmpty else {
layer.borderColor = nil
return
}
let gradient = UIImage.imageGradient(bounds: bounds, colors: borderGradientColors)
layer.borderColor = UIColor(patternImage: gradient).cgColor
}
}
extension UIImage {
static func imageGradient(bounds: CGRect, colors: [UIColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors.map(\.cgColor)
// This makes it left to right, default is top to bottom
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { gradientLayer.render(in: $0.cgContext) }
}
}
Tough to say why you would be getting crashes, unless you can provide code that reproduces it.
However, you may find using a
CAGradientLayerwith a.maskto be a better, more efficient option:Edit -- addressing the need for rounded corners...
There are a number of bugs with
UIBezierPath(roundedRect: ....The biggest problems come into play when the
cornerRadiusis roughly 1/3rd of the short-side.So, if the frame is
300 x 60(perhaps to provide a border for a label), and we have acornerRadiusof20, things can get really ugly.Instead of overwhelming with details, refer to these SO posts (among others):
Now, IF you will always have a clear background, and IF you will never approach the 1/3rd radius-to-side ratio, using
UIBezierPath(roundedRect: ...with the above code should not be a problem.However, here is another approach to your design goal that you may find more reliable and flexible...
We will keep the view background color clear, add a "fillLayer" for the desired background color, and we'll mask the gradient layer with a "plain"
CALayerwith it's border properties set.Example controller
Output - note that on the first and third instances, I used
.black.withAlphaComponent(0.20)for the background color instead of.clear-- otherwise when we toggle off the gradient border we wouldn't see anything:tapping anywhere toggles the gradient border: