22
Jul
2021
Stroke and fill a Shape in SwiftUI
Reading time: 1 min
For whatever reason, stroking (i.e drawing a border) and filling a SwiftUI Shape
at the same time is difficult and unintuitive. Shape
has methods for both, stroke
and fill
, respectively, but they both return some View
, meaning you can't chain them.
Say you want to do the following:
The proper way is to set the stroke first and then put the same view in background
:
Circle()
.stroke(Color.red, lineWidth: 7)
.background(Circle().fill(Color.green))
Doing this over and over again can be tiresome, so here's a neat extension that condenses it all into a single method:
// Need this to let the extension know that the Shape
// can be instantiated without additional params.
protocol ParameterlessInitable {
init()
}
// Make existing Shapes conform to the new protocol.
extension Circle: ParameterlessInitable { }
extension Rectangle: ParameterlessInitable { }
extension Capsule: ParameterlessInitable {
init() {
self.init(style: .circular)
}
}
extension Shape where Self: ParameterlessInitable {
func stroke<StrokeStyle, FillStyle>(
_ strokeStyle: StrokeStyle,
lineWidth: CGFloat = 1,
fill fillStyle: FillStyle
) -> some View where StrokeStyle: ShapeStyle, FillStyle: ShapeStyle {
Self()
.stroke(strokeStyle, lineWidth: lineWidth)
.background(Self().fill(fillStyle))
}
}
Then, you can simply write it as following:
Circle()
.stroke(Color.red, lineWidth: 7, fill: Color.green)
.frame(width: 60, height: 60)
Capsule()
.stroke(Color.blue, lineWidth: 5, fill: Color.orange)
.frame(width: 100, height: 60)
Rectangle()
.stroke(Color.black, lineWidth: 10, fill: Color.white)
.frame(width: 100, height: 60)