Runde spezifische Ecken SwiftUI

76

Ich weiß, dass Sie .cornerRadius()alle Ecken einer SwiftUI-Ansicht abrunden können, aber gibt es eine Möglichkeit, nur bestimmte Ecken wie die Oberseite abzurunden?

Richard Witherspoon
quelle

Antworten:

68

Es gibt zwei Optionen: Sie können a Viewmit a verwenden Pathoder eine benutzerdefinierte erstellen Shape. In beiden Fällen können Sie sie eigenständig oder in einem verwenden.background(RoundedCorders(...))

Geben Sie hier die Bildbeschreibung ein

Option 1: Verwenden von Path + GeometryReader

(Weitere Informationen zu 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: Benutzerdefinierte Form

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
    }
}
Kon Tiki
quelle
Bitte. Wenn Sie der Meinung sind, dass Ihre Frage beantwortet wurde, denken Sie daran, sie zu akzeptieren (klicken Sie auf das grüne Häkchen). Es hilft anderen Benutzern, Antworten schneller zu finden. Prost!
Kontiki
Ich habe die Antwort aktualisiert und einen Link zu einem detaillierten Artikel eingefügt, den ich über GeometryReader und GeometryProxy geschrieben habe.
Kontiki
Wenn Sie Shapestattdessen eine benutzerdefinierte Version definieren , müssen Sie diese nicht einbeziehen GeometryReader.
Rob Mayoff
Vielen Dank! Wir waren so jung. Ich weiß es jetzt besser ;-) Ich werde die Antwort aktualisieren, sobald ich etwas Zeit habe.
Kontiki
Nur eine kleine Korrektur zu Option 2: Ich denke, der Pfad beginnt mit dem falschen x-Wert, da er die obere Linie in der linken Hälfte abschneiden soll. Ich habe den Pfadstartpunkt auf geändert path.move(to: CGPoint(x: tl, y: 0))und das schien das Problem zu beheben.
Alex H
237

Verwendung als benutzerdefinierter Modifikator

Sie können es wie einen normalen Modifikator verwenden:

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

Demo

Geben Sie hier die Bildbeschreibung ein

Sie müssen eine einfache Erweiterung Viewwie folgt implementieren :

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

Und hier ist die Struktur dahinter:

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

Sie können die Form auch direkt als Schnittmaske verwenden.


Beispielprojekt:

Stichprobe

Mojtaba Hosseini
quelle
28
Diese Lösung ist weitaus sauberer als die akzeptierte.
Reda Lemeden
1
Kasse diese Antwort für benutzerdefinierte Grenze @SorinLica
Mojtaba Hosseini
1
Wissen Sie, wie dies in einer SwiftUI-Ansicht für macOS (nicht Catalyst) implementiert werden würde? Sieht so aus, als hätte NSRectes kein äquivalentes Eckobjekt und NSBezierPathkeinen byRoundingCornersParameter.
TheNeil
3
Es funktionierte gut bis ios14, Blick von unten verschwindet
shanezzar
1
Es funktioniert nicht mehr richtig in iOS14, ich hatte einige Layoutprobleme damit.
Cinn
33

View Modifiers machten es einfach:

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

Beispiel:

Geben Sie hier die Bildbeschreibung ein

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

//right Button
.cornerRadius(radius: 6, corners: [.topRight, .bottomRight])
Peter Kreinz
quelle
1
Sehr elegant.
Ich
Wissen Sie, wie dies in einer SwiftUI-Ansicht für macOS (nicht Catalyst) implementiert werden würde? Sieht so aus, als hätte NSRectes kein äquivalentes Eckobjekt und NSBezierPathkeinen byRoundingCornersParameter.
TheNeil
@ TheNeil Sorry, ich habe mich noch nicht mit 'macOS' befasst
Peter Kreinz
Tolle Lösung! Danke für den Zusatz.
Richard Witherspoon
Verwendet sonst noch jemand diese oder die oben genannte Version unter iOS 14? Ich finde, es schneidet jede Bildlaufansicht an den Rändern ab - der gleiche Code läuft gut auf iOS 13-Geräten / Simulatoren.
Richard Groves
11

Eine andere Option (vielleicht besser) besteht darin, dafür zu UIKIt zurückzukehren. Z.B:

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)
    }
}
orj
quelle
Was ist var style hier zu tun?
Jeevan