问题
This is a self-answered question which is perfectly acceptable (and even encouraged) on Stack Overflow. The point is to share something useful to others.
SwiftUI has a RoundedRectangle
Shape
. It would be nice to have a five-pointed star with rounded tips that could be used for filling, clipping, and animation.
This Stack Overflow answer shows how to make a RoundedStar
as a custom UIView
using UIBezierPath
.
How can this code be adapted to SwiftUI
as a Shape
that can be animated?
回答1:
Here is the RoundedStar
code adapted as an animatable SwiftUI Shape
:
// Five-point star with rounded tips
struct RoundedStar: Shape {
var cornerRadius: CGFloat
var animatableData: CGFloat {
get { return cornerRadius }
set { cornerRadius = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
let r = rect.width / 2
let rc = cornerRadius
let rn = r * 0.95 - rc
// start angle at -18 degrees so that it points up
var cangle = -18.0
for i in 1 ... 5 {
// compute center point of tip arc
let cc = CGPoint(x: center.x + rn * CGFloat(cos(Angle(degrees: cangle).radians)), y: center.y + rn * CGFloat(sin(Angle(degrees: cangle).radians)))
// compute tangent point along tip arc
let p = CGPoint(x: cc.x + rc * CGFloat(cos(Angle(degrees: cangle - 72).radians)), y: cc.y + rc * CGFloat(sin(Angle(degrees: (cangle - 72)).radians)))
if i == 1 {
path.move(to: p)
} else {
path.addLine(to: p)
}
// add 144 degree arc to draw the corner
path.addArc(center: cc, radius: rc, startAngle: Angle(degrees: cangle - 72), endAngle: Angle(degrees: cangle + 72), clockwise: false)
// Move 144 degrees to the next point in the star
cangle += 144
}
return path
}
}
The code is very similar to the UIBezierPath
version except that it uses the new Angle
type which provides easy access to both degrees
and radians
. The code to draw the star rotated was removed because it is easy to add rotation to a SwiftUI shape with the .rotationEffect(angle:)
view modifier.
Demonstration:
Here is a demonstration that show the animatable qualities of the cornerRadius
setting as well as showing what the various cornerRadius
settings look like on a full-screen star.
struct ContentView: View {
@State private var radius: CGFloat = 0.0
var body: some View {
ZStack {
Color.blue.edgesIgnoringSafeArea(.all)
VStack(spacing: 40) {
Spacer()
RoundedStar(cornerRadius: radius)
.aspectRatio(1, contentMode: .fit)
.foregroundColor(.yellow)
.overlay(Text(" cornerRadius: \(Int(self.radius)) ").font(.body))
HStack {
ForEach([0, 10, 20, 40, 80, 200], id: \.self) { value in
Button(String(value)) {
withAnimation(.easeInOut(duration: 0.3)) {
self.radius = CGFloat(value)
}
}
.frame(width: 50, height: 50)
.foregroundColor(.black)
.background(Color.yellow.cornerRadius(8))
}
}
Spacer()
}
}
}
}
Running in Swift Playgrounds on iPad
This runs beautifully on an iPad in the Swift Playgrounds app. Just add:
import PlaygroundSupport
at the top and
PlaygroundPage.current.setLiveView(ContentView())
at the end.
Using the RoundedStar shape to create EU Flag
struct ContentView: View {
let radius: CGFloat = 100
let starWidth: CGFloat = 36
let numStars = 12
var body: some View {
ZStack {
Color.blue
ForEach(0..<numStars) { n in
RoundedStar(cornerRadius: 0)
.frame(width: starWidth, height: starWidth)
.offset(x: radius * cos(CGFloat(n) / CGFloat(numStars) * 2 * .pi), y: radius * sin(CGFloat(n) / CGFloat(numStars) * 2 * .pi))
.foregroundColor(.yellow)
}
}
}
}
来源:https://stackoverflow.com/questions/63650216/how-to-create-a-swiftui-roundedstar-shape