Comment créer une forme SwiftUI RoundedStar?

Aug 30 2020

C'est une question auto-répondue qui est parfaitement acceptable (et même encouragée) sur Stack Overflow. Le but est de partager quelque chose d'utile avec les autres.

SwiftUI a un RoundedRectangle Shape. Ce serait bien d'avoir une étoile à cinq branches avec des pointes arrondies qui pourraient être utilisées pour le remplissage, le découpage et l'animation.

Cette réponse Stack Overflow montre comment créer une utilisation RoundedStarpersonnalisée .UIViewUIBezierPath

Comment ce code peut-il être adapté en SwiftUItant que Shapequi peut être animé?

Réponses

3 vacawama Aug 30 2020 at 01:04

Voici le RoundedStarcode adapté en SwiftUI animable 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
    }
}

Le code est très similaire à la UIBezierPathversion sauf qu'il utilise le nouveau Angletype qui fournit un accès facile à la fois degreeset radians. Le code pour dessiner l'étoile pivotée a été supprimé car il est facile d'ajouter une rotation à une forme SwiftUI avec le .rotationEffect(angle:)modificateur de vue.


Manifestation:

Voici une démonstration qui montre les qualités animables du cornerRadiusdécor et montre à quoi ressemblent les différents cornerRadiusréglages sur une étoile en plein écran.

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

Exécution dans Swift Playgrounds sur iPad

Cela fonctionne à merveille sur un iPad dans l'application Swift Playgrounds. Il suffit d'ajouter:

import PlaygroundSupport

en haut et

PlaygroundPage.current.setLiveView(ContentView())

à la fin.


Utilisation de la forme RoundedStar pour créer un drapeau européen

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