Como criar uma forma RoundedStar SwiftUI?

Aug 30 2020

Esta é uma pergunta auto-respondida que é perfeitamente aceitável (e até encorajada) no Stack Overflow. O objetivo é compartilhar algo útil para os outros.

SwiftUI tem um arquivo RoundedRectangle Shape. Seria bom ter uma estrela de cinco pontas com pontas arredondadas que pudesse ser usada para preenchimento, recorte e animação.

Esta resposta do Stack Overflow mostra como criar RoundedStarum personalizado UIViewusando UIBezierPath.

Como este código pode ser adaptado SwiftUIcomo um Shapeque pode ser animado?

Respostas

3 vacawama Aug 30 2020 at 01:04

Aqui está o RoundedStarcódigo adaptado como um SwiftUI animável 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
    }
}

O código é muito semelhante à UIBezierPathversão, exceto que ele usa o novo Angletipo que fornece acesso fácil a ambos degreese radians. O código para desenhar a estrela girada foi removido porque é fácil adicionar rotação a uma forma SwiftUI com o .rotationEffect(angle:)modificador de visualização.


Demonstração:

Aqui está uma demonstração que mostra as qualidades animáveis ​​da cornerRadiusconfiguração, bem como mostra como as várias cornerRadiusconfigurações se parecem em uma estrela em tela cheia.

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

Correndo no Swift Playgrounds no iPad

Isso funciona lindamente em um iPad no aplicativo Swift Playgrounds. Basta adicionar:

import PlaygroundSupport

no topo e

PlaygroundPage.current.setLiveView(ContentView())

no final.


Usando a forma RoundedStar para criar a bandeira da 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)
            }
        }
    }
}