圆角特定SwiftUI


Answers:


68

有两个选项,您可以将View搭配使用Path,也可以创建自定义Shape。在这两种情况下,您都可以单独使用它们,也可以在.background(RoundedCorders(...))

在此处输入图片说明

选项1:使用Path + GeometryReader

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

选项2:自定义形状

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

别客气。如果您认为您的问题已得到回答,请记住接受它(单击绿色的对勾)。它可以帮助其他用户更快地找到答案。干杯!
kontiki

我更新了答案,以包含指向我撰写的有关GeometryReader和GeometryProxy的详细文章的链接。
kontiki

如果您定义一个自定义Shape,则不必涉及GeometryReader
rob mayoff

谢谢!我们还很年轻。我现在知道的更好;-)我一会儿就会更新答案。
kontiki

只是对选项2的小幅修正,我认为路径以错误的x值开头,因为它看起来会切断左半部分的顶行。我将路径起点更改为path.move(to: CGPoint(x: tl, y: 0)),似乎已经解决了。
Alex H

239

用作自定义修饰符

您可以像普通修饰符一样使用它:

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

演示版

在此处输入图片说明

您需要View像这样实现一个简单的扩展:

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

这是其背后的结构:

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

您也可以将形状直接用作剪贴蒙版。


示例项目:

样品


28
此解决方案比公认的解决方案干净得多。
Reda Lemeden

1
签出自定义边框@SorinLica的答案
Mojtaba Hosseini

1
您知道如何在macOS(而非Catalyst)的SwiftUI视图中实现吗?看起来NSRect没有等效的角对象,并且NSBezierPath没有byRoundingCorners参数。
TheNeil

3
直到ios14为止一切正常,从底部开始消失的
景象

1
它在iOS14中不再正常工作,我在布局方面遇到了一些问题。
Cinn

33

查看修饰符使操作变得简单:

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

例:

在此处输入图片说明

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

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

1
十分优雅。喜欢它
TheNeil

您知道如何在macOS(而非Catalyst)的SwiftUI视图中实现吗?看起来NSRect没有等效的角对象,并且NSBezierPath没有byRoundingCorners参数。
TheNeil

@TheNeil对不起,我没有处理“的MacOS”尚未
彼得Kreinz

很好的解决方案!感谢您的添加。
理查德·威瑟斯庞

还有其他人使用此版本,还是iOS 14或以上版本?我发现它将所有滚动视图都修剪到了边缘-相同的代码在iOS 13设备/模拟器上运行良好。
理查德·格罗夫斯

11

另一个选择(也许更好)实际上是为此返回UIKIt。例如:

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

这不是var style 什么?
耶万(Jeevan)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.