Jak stworzyć SwiftUI RoundedStar Shape?

Aug 30 2020

To pytanie, na które można odpowiedzieć samodzielnie, jest całkowicie akceptowalne (a nawet zalecane) w witrynie Stack Overflow. Chodzi o to, aby podzielić się czymś pożytecznym dla innych.

SwiftUI ma RoundedRectangle Shape. Byłoby miło mieć pięcioramienną gwiazdę z zaokrąglonymi końcówkami, których można by użyć do wypełniania, przycinania i animacji.

Ta odpowiedź na przepełnienie stosu pokazuje, jak utworzyć RoundedStarniestandardowy plik UIViewusing UIBezierPath.

Jak to może być dostosowany do kodu SwiftUIjako Shapeże może być animowany?

Odpowiedzi

3 vacawama Aug 30 2020 at 01:04

Oto RoundedStarkod zaadaptowany jako animowany interfejs 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
    }
}

Kod jest bardzo podobny do UIBezierPathwersji, z tym wyjątkiem, że używa nowego Angletypu, który zapewnia łatwy dostęp do obu degreesi radians. Kod do rysowania obróconej gwiazdy został usunięty, ponieważ łatwo jest dodać obrót do kształtu SwiftUI za pomocą .rotationEffect(angle:)modyfikatora widoku.


Demonstracja:

Oto demonstracja, która pokazuje animowane cechy cornerRadiusscenerii, a także pokazuje, jak różne cornerRadiusustawienia wyglądają na gwiazdę pełnoekranową.

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

Bieganie w Swift Playgrounds na iPadzie

Działa to pięknie na iPadzie w aplikacji Swift Playgrounds. Poprostu dodaj:

import PlaygroundSupport

u góry i

PlaygroundPage.current.setLiveView(ContentView())

na końcu.


Używanie kształtu RoundedStar do tworzenia flagi UE

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