问题
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