In UIKit drawing a stroked & filled path/shape is pretty easy.
Eg, the code below draws a red circle that is stroked in blue.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
let center = CGPoint(x: rect.midX, y: rect.midY)
ctx.setFillColor(UIColor.red.cgColor)
ctx.setStrokeColor(UIColor.blue.cgColor)
let arc = UIBezierPath(arcCenter: center, radius: rect.width/2, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
arc.stroke()
arc.fill()
}
How does one do this with SwiftUI?
Swift UI seems to support:
Circle().stroke(Color.blue)
// and/or
Circle().fill(Color.red)
but not
Circle().fill(Color.red).stroke(Color.blue) // Value of type 'ShapeView<StrokedShape<Circle>, Color>' has no member 'fill'
// or
Circle().stroke(Color.blue).fill(Color.red) // Value of type 'ShapeView<Circle, Color>' has no member 'stroke'
Am I supposed to just ZStack two circles? That seems a bit silly.
You can draw a circle with stroke on top of a filled circle
struct ContentView: View {
var body: some View {
Circle()
.overlay(
Circle()
.stroke(Color.green,lineWidth: 5)
).foregroundColor(Color.red)
}
}
Seems like it's either ZStack
or .overlay
at the moment.
The view hierarchy is almost identical - according to Xcode.
struct ContentView: View {
var body: some View {
VStack {
Circle().fill(Color.red)
.overlay(Circle().stroke(Color.blue))
ZStack {
Circle().fill(Color.red)
Circle().stroke(Color.blue)
}
}
}
}
Output:
View hierarchy:
For future reference, @Imran's solution works, but you also need to account for stroke width in your total frame by padding:
struct Foo: View {
private let lineWidth: CGFloat = 12
var body: some View {
Circle()
.stroke(Color.purple, lineWidth: self.lineWidth)
.overlay(
Circle()
.fill(Color.yellow)
)
.padding(self.lineWidth)
}
}
I put the following wrapper together based on the answers above. It makes this a bit more easy and the code a bit more simple to read.
struct FillAndStroke<Content:Shape> : View
{
let fill : Color
let stroke : Color
let content : () -> Content
init(fill : Color, stroke : Color, @ViewBuilder content : @escaping () -> Content)
{
self.fill = fill
self.stroke = stroke
self.content = content
}
var body : some View
{
ZStack
{
content().fill(self.fill)
content().stroke(self.stroke)
}
}
}
It can be used like this:
FillAndStroke(fill : Color.red, stroke : Color.yellow)
{
Circle()
}
Hopefully Apple will find a way to support both fill and stroke on a shape in the future.
My workaround:
import SwiftUI
extension Shape {
/// fills and strokes a shape
public func fill<S:ShapeStyle>(
_ fillContent: S,
stroke : StrokeStyle
) -> some View {
ZStack {
self.fill(fillContent)
self.stroke(style:stroke)
}
}
}
Example:
struct ContentView: View {
// fill gradient
let gradient = RadialGradient(
gradient : Gradient(colors: [.yellow, .red]),
center : UnitPoint(x: 0.25, y: 0.25),
startRadius: 0.2,
endRadius : 200
)
// stroke line width, dash
let w: CGFloat = 6
let d: [CGFloat] = [20,10]
// view body
var body: some View {
HStack {
Circle()
// ⭐️ Shape.fill(_:stroke:)
.fill(Color.red, stroke: StrokeStyle(lineWidth:w, dash:d))
Circle()
.fill(gradient, stroke: StrokeStyle(lineWidth:w, dash:d))
}.padding().frame(height: 300)
}
}
Result: