SwiftUI:如何绘制填充和描边形状?

78
在UIKit中,绘制描边和填充的路径/形状非常容易。
例如,下面的代码会绘制一个红色圆,描边为蓝色。
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()
}

如何使用SwiftUI完成这个任务?

看起来SwiftUI支持以下功能:

Circle().stroke(Color.blue)
// and/or
Circle().fill(Color.red)
但不。
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'

我只需要在ZStack中堆叠两个圆形吗?这看起来有点傻。


请查看 https://www.swiftbysundell.com/articles/stroking-and-filling-a-swiftui-shape-at-the-same-time/。 - Robin Stewart
12个回答

1

这里是我用于填充和描边形状的扩展。其他答案都不允许完全自定义填充和描边样式。

extension Shape {
    
    /// Fills and strokes a shape.
    func style<F: ShapeStyle, S: ShapeStyle>(
        fill: F,
        stroke: S,
        strokeStyle: StrokeStyle
    ) -> some View {
        ZStack {
            self.fill(fill)
            self.stroke(stroke, style: strokeStyle)
        }
    }
    
    /// Fills and strokes a shape.
    func style<F: ShapeStyle, S: ShapeStyle>(
        fill: F,
        stroke: S,
        lineWidth: CGFloat = 1
    ) -> some View {
        self.style(
            fill: fill,
            stroke: stroke,
            strokeStyle: StrokeStyle(lineWidth: lineWidth)
        )
    }
    
}

extension InsettableShape {
    
    /// Fills and strokes an insettable shape.
    func style<F: ShapeStyle, S: ShapeStyle>(
        fill: F,
        strokeBorder: S,
        strokeStyle: StrokeStyle
    ) -> some View {
        ZStack {
            self.fill(fill)
            self.strokeBorder(strokeBorder, style: strokeStyle)
        }
    }
    
    /// Fills and strokes an insettable shape.
    func style<F: ShapeStyle, S: ShapeStyle>(
        fill: F,
        strokeBorder: S,
        lineWidth: CGFloat = 1
    ) -> some View {
        self.style(
            fill: fill,
            strokeBorder: strokeBorder,
            strokeStyle: StrokeStyle(lineWidth: lineWidth)
        )
    }
    
}

0

有几种方法可以实现“填充和描边”的效果。以下是其中的三种:

struct ContentView: View {
    var body: some View {
        let shape = Circle()
        let gradient = LinearGradient(gradient: Gradient(colors: [.orange, .red, .blue, .purple]), startPoint: .topLeading, endPoint: .bottomTrailing)
        VStack {
            Text("Most modern way (for simple backgrounds):")
            shape
                .strokeBorder(Color.green,lineWidth: 6)
                .background(gradient, in: shape) // Only `ShapeStyle` as background can be used (iOS15)
            Text("For simple backgrounds:")
            shape
                .strokeBorder(Color.green,lineWidth: 6)
                .background(
                    ZStack { // We are pretty limited with `shape` if we need to keep inside border
                       shape.fill(gradient) // Only `Shape` Views as background
                       shape.fill(.yellow).opacity(0.4) // Another `Shape` view
                       //Image(systemName: "star").resizable() //Try to uncomment and see the star spilling of the border
                    }
                )
            Text("For any content to be clipped:")
            shape
                .strokeBorder(Color.green,lineWidth: 6)
                .background(Image(systemName: "star").resizable()) // Anything
                .clipShape(shape) // clips everything
        }
    }
}

对于某些情况下的两个形状(描边和填充)进行ZStack组合对我来说也不是一个坏主意。

如果您想使用命令式方法,这里有一个小的Playground示例,可以使用Canvas视图。折衷方案是,您无法将手势附加到在Canvas上绘制的形状和对象,只能附加到Canvas本身。

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    let lineWidth: CGFloat = 8
    var body: some View {
        Canvas { context, size in
            let path = Circle().inset(by: lineWidth / 2).path(in: CGRect(origin: .zero, size: size))
            context.fill(path, with: .color(.cyan))
            context.stroke(path, with: .color(.yellow), style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, dash: [30,20]))
        }
        .frame(width: 100, height: 200)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接