I want to make the outlines of a UIView look "wavey" like someone drew them.
I have this example from PowerPoint, which allows to do it (should work with any size and corner radius):
Currently this is what I have:
myView.layer.borderWidth = 10
myView.layer.borderColor = UIColor.blue.cgColor
myView.layer.cornerRadius = 5 // Optional
Thank
You can create "wavy" lines by using a UIBezierPath
with a combination of quad-curves, lines, arcs, etc.
We'll start with a simple line, one-quarter of the width of the view:
Our path would consist of:
0,0
80,0
If we change that to a quad-curve:
Now we're doing:
0,0
80,0
with control point 40,40
If we add another quad-curve going the other way:
Now we're doing:
0,0
80,0
with control point 40,40
160,0
with control point 120,-40
and we can extend that the width of the view:
of course, that doesn't look like your "sketch" target, so let's change the control-point offsets from 40 to 2:
Now it looks a bit more like a hand-draw "sketched" line.
It's too uniform, though, and it's partially outside the bounds of the view, so let's inset it by 8-pts and, instead of four 25% segments, we'll use (for example) five segments of these widths:
0.15, 0.2, 0.2, 0.27, 0.18
If we take the same approach to go down the right-hand side, back across the bottom, and up the left-hand side, we can get this:
Here's some example code to produce that view:
class SketchBorderView: UIView {
let borderLayer: CAShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.blue.cgColor
layer.addSublayer(borderLayer)
backgroundColor = .yellow
}
override func layoutSubviews() {
let incrementVals: [CGFloat] = [
0.15, 0.2, 0.2, 0.27, 0.18,
]
let lineOffsets: [[CGFloat]] = [
[ 1.0, -2.0],
[-1.0, 2.0],
[-1.0, -2.0],
[ 1.0, 2.0],
[ 0.0, -2.0],
]
let pth: UIBezierPath = UIBezierPath()
// inset bounds by 8-pts so we can draw the "wavy border"
// inside our bounds
let r: CGRect = bounds.insetBy(dx: 8.0, dy: 8.0)
var ptDest: CGPoint = .zero
var ptControl: CGPoint = .zero
// start at top-left
ptDest = r.origin
pth.move(to: ptDest)
// we're at top-left
for i in 0..<incrementVals.count {
ptDest.x += r.width * incrementVals[i]
ptDest.y = r.minY + lineOffsets[i][0]
ptControl.x = pth.currentPoint.x + ((ptDest.x - pth.currentPoint.x) * 0.5)
ptControl.y = r.minY + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at top-right
for i in 0..<incrementVals.count {
ptDest.y += r.height * incrementVals[i]
ptDest.x = r.maxX + lineOffsets[i][0]
ptControl.y = pth.currentPoint.y + ((ptDest.y - pth.currentPoint.y) * 0.5)
ptControl.x = r.maxX + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at bottom-right
for i in 0..<incrementVals.count {
ptDest.x -= r.width * incrementVals[i]
ptDest.y = r.maxY + lineOffsets[i][0]
ptControl.x = pth.currentPoint.x - ((pth.currentPoint.x - ptDest.x) * 0.5)
ptControl.y = r.maxY + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
// now we're at bottom-left
for i in 0..<incrementVals.count {
ptDest.y -= r.height * incrementVals[i]
ptDest.x = r.minX + lineOffsets[i][0]
ptControl.y = pth.currentPoint.y - ((pth.currentPoint.y - ptDest.y) * 0.5)
ptControl.x = r.minX + lineOffsets[i][1]
pth.addQuadCurve(to: ptDest, controlPoint: ptControl)
}
borderLayer.path = pth.cgPath
}
}
and an example controller:
class SketchTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = SketchBorderView()
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: g.centerXAnchor),
v.centerYAnchor.constraint(equalTo: g.centerYAnchor),
v.widthAnchor.constraint(equalToConstant: 320.0),
v.heightAnchor.constraint(equalTo: v.widthAnchor),
])
}
}
Using that code, though, we still have too much uniformity, so in actual use we'd want to randomize the number of segments, the widths of the segments, and the control-point offsets.
Of course, to get your "rounded rect" you'd want to add arcs at the corners.
I expect this should get you on your way though.