253 votes

Coins arrondis spécifiques SwiftUI

Je sais que vous pouvez utiliser .cornerRadius() pour arrondir tous les coins d'une vue swiftUI, mais existe-t-il un moyen d'arrondir uniquement des coins spécifiques, comme le haut ?

1 votes

J'ai fini par laisser tomber SwiftUI car, quoi que je fasse, les performances étaient terribles. Au final, j'ai fini par utiliser la propriété maskedCorners du CALayer de ma vue UIKit représentable.

793voto

Mojtaba Hosseini Points 2525

Utilisation comme modificateur personnalisé

Vous pouvez l'utiliser comme un modificateur normal :

.cornerRadius(20, corners: [.topLeft, .bottomRight])

Démo

enter image description here

Vous devez implémenter une simple extension sur View así:

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape( RoundedCorner(radius: radius, corners: corners) )
    }
}

Et voici la structure derrière cela :

struct RoundedCorner: Shape {

    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

Vous pouvez également utiliser la forme directement comme masque d'écrêtage.


Exemple de projet :

Sample

0 votes

Bonjour @Mojtaba, j'aime cette solution. Est-ce que le var style: RoundedCornerStyle = .continuous est réellement utilisé ici ?

77 votes

Cette solution est bien plus propre que celle qui a été acceptée.

1 votes

Comment ajouter une bordure à la même vue ?

124voto

kontiki Points 633

Il y a deux options, vous pouvez utiliser un View avec un Path ou vous pouvez créer une Shape . Dans les deux cas, vous pouvez les utiliser de manière autonome, ou dans une .background(RoundedCorders(...))

enter image description here

Option 1 : Utilisation de Path + GeometryReader

(plus d'informations sur GeometryReader : https://swiftui-lab.com/geometryreader-to-the-rescue/ )

struct ContentView : View {
    var body: some View {

        Text("Hello World!")
            .foregroundColor(.white)
            .font(.largeTitle)
            .padding(20)
            .background(RoundedCorners(color: .blue, tl: 0, tr: 30, bl: 30, br: 0))
    }
}

struct RoundedCorners: View {
    var color: Color = .blue
    var tl: CGFloat = 0.0
    var tr: CGFloat = 0.0
    var bl: CGFloat = 0.0
    var br: CGFloat = 0.0

    var body: some View {
        GeometryReader { geometry in
            Path { path in

                let w = geometry.size.width
                let h = geometry.size.height

                // Make sure we do not exceed the size of the rectangle
                let tr = min(min(self.tr, h/2), w/2)
                let tl = min(min(self.tl, h/2), w/2)
                let bl = min(min(self.bl, h/2), w/2)
                let br = min(min(self.br, h/2), w/2)

                path.move(to: CGPoint(x: w / 2.0, y: 0))
                path.addLine(to: CGPoint(x: w - tr, y: 0))
                path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
                path.addLine(to: CGPoint(x: w, y: h - br))
                path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
                path.addLine(to: CGPoint(x: bl, y: h))
                path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
                path.addLine(to: CGPoint(x: 0, y: tl))
                path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
            }
            .fill(self.color)
        }
    }
}

Option 2 : Forme personnalisée

struct ContentView : View {
    var body: some View {

        Text("Hello World!")
            .foregroundColor(.white)
            .font(.largeTitle)
            .padding(20)
            .background(RoundedCorners(tl: 0, tr: 30, bl: 30, br: 0).fill(Color.blue))
    }
}

struct RoundedCorners: Shape {
    var tl: CGFloat = 0.0
    var tr: CGFloat = 0.0
    var bl: CGFloat = 0.0
    var br: CGFloat = 0.0

    func path(in rect: CGRect) -> Path {
        var path = Path()

        let w = rect.size.width
        let h = rect.size.height

        // Make sure we do not exceed the size of the rectangle
        let tr = min(min(self.tr, h/2), w/2)
        let tl = min(min(self.tl, h/2), w/2)
        let bl = min(min(self.bl, h/2), w/2)
        let br = min(min(self.br, h/2), w/2)

        path.move(to: CGPoint(x: w / 2.0, y: 0))
        path.addLine(to: CGPoint(x: w - tr, y: 0))
        path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr,
                    startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)

        path.addLine(to: CGPoint(x: w, y: h - br))
        path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br,
                    startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)

        path.addLine(to: CGPoint(x: bl, y: h))
        path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl,
                    startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)

        path.addLine(to: CGPoint(x: 0, y: tl))
        path.addArc(center: CGPoint(x: tl, y: tl), radius: tl,
                    startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)

        return path
    }
}

0 votes

Si vous définissez une Shape au lieu de cela, vous n'avez pas besoin d'impliquer GeometryReader .

0 votes

Juste une petite correction sur l'option 2 : je pense que le chemin commence à la mauvaise valeur x puisqu'il semble couper la ligne supérieure dans sa moitié gauche. J'ai changé le point de départ du chemin en path.move(to: CGPoint(x: tl, y: 0)) et ça semblait régler le problème.

0 votes

Ce n'est pas aussi propre que les réponses ci-dessous, mais c'est la seule qui fonctionne à partir d'iOS 14 lorsque je veux arrondir 3 coins. L'autre méthode finit par arrondir les 4 coins lorsque je veux les arrondir à .infinity

85voto

screenworker Points 31

Voir les modificateurs a facilité les choses :

struct CornerRadiusStyle: ViewModifier {
    var radius: CGFloat
    var corners: UIRectCorner

    struct CornerRadiusShape: Shape {

        var radius = CGFloat.infinity
        var corners = UIRectCorner.allCorners

        func path(in rect: CGRect) -> Path {
            let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
            return Path(path.cgPath)
        }
    }

    func body(content: Content) -> some View {
        content
            .clipShape(CornerRadiusShape(radius: radius, corners: corners))
    }
}

extension View {
    func cornerRadius(radius: CGFloat, corners: UIRectCorner) -> some View {
        ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
    }
}

Exemple :

enter image description here

//left Button
.cornerRadius(radius: 6, corners: [.topLeft, .bottomLeft])

//right Button
.cornerRadius(radius: 6, corners: [.topRight, .bottomRight])

1 votes

Savez-vous comment cela pourrait être mis en œuvre dans une vue SwiftUI pour macOS (pas Catalyst) ? On dirait que NSRect n'a pas d'objet de coin équivalent, et NSBezierPath n'a pas le byRoundingCorners paramètre.

0 votes

Quelqu'un d'autre utilise-t-il cette version, ou la version précédente, sous iOS 14 ? Je trouve que cela clive tout scrollview vers les bords - le même code fonctionne bien sur les appareils/simulateurs iOS 13.

0 votes

Bonjour, @RichardGroves, j'ai rencontré exactement le même problème que vous. Voir ma réponse ici : stackoverflow.com/a/64571117/4733603

12voto

orj Points 4480

Une autre option (peut-être meilleure) est de revenir à UIKIt pour cela. Par exemple :

struct ButtonBackgroundShape: Shape {

    var cornerRadius: CGFloat
    var style: RoundedCornerStyle

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        return Path(path.cgPath)
    }
}

1 votes

Qu'est-ce que var style n'est pas ici ?

1voto

Simon Bergmark Points 1

J'aimerais compléter la réponse de Kontiki ;

Si vous utilisez l'option 2 et que vous souhaitez ajouter un trait à la forme, veillez à ajouter ce qui suit juste avant de renvoyer le chemin :

path.addLine(to: CGPoint(x: w/2.0, y: 0))

Sinon, le trait sera brisé du coin supérieur gauche au milieu du côté supérieur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X