SwiftUI: How to draw filled and stroked shape?

2020-03-10 05:42发布

问题:

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.

回答1:

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)
   }
}



回答2:

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:



回答3:

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)
    }
}



回答4:

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.



回答5:

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:



标签: swift swiftui