如何圆化视图的特定角?

问题描述 投票:0回答:11

我知道你可以使用

.cornerRadius()
来圆化 SwiftUI 视图的所有角,但是有没有办法只圆化特定的角,例如顶部?

user-interface rounding swiftui cornerradius
11个回答
815
投票
演示(源代码可在帖子末尾获得)

Demo Image

iOS 16+ 内置修改器(需要 Xcode 15)

使用新的

UnevenRoundedRectangle
剪辑视图:

.clipShape(
    .rect(
        topLeadingRadius: 0,
        bottomLeadingRadius: 20,
        bottomTrailingRadius: 0,
        topTrailingRadius: 20
    )
)

⚠️注意:虽然它可以在 iOS 16 上运行,但

.rect
需要 Xcode 15 才能使用。


iOS 13+

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

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

您还可以直接使用形状作为剪贴蒙版。


示例项目:

iOS 13 - 要点上的源代码 Sample iOS 14


iOS 16 - 要点上的源代码 Sample iOS 16


126
投票

有两个选项,您可以将

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)
                path.closeSubpath()
            }
            .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)
        path.closeSubpath()

        return path
    }
}

86
投票

视图修改器让一切变得简单:

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(6, corners: [.topLeft, .bottomLeft])

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

65
投票

如果您只需要将顶角圆化 - 在 100 种情况中的 99 种情况下,我确信这正是您正在寻找的 - 对于该问题有一个更简单的解决方案。工作原理如下:

  1. 在视图底部添加一些填充
  2. .cornerRadius(_:)
  3. 修整所有角
  4. 通过应用相同值的负填充来删除填充
struct OnlyTopRoundedCornersDemo: View {
    let radius = 12 // radius we need
    var body: some View {
        Rectangle()
            .frame(height: 50)
            .foregroundColor(.black)
        .padding(.bottom, radius)
        .cornerRadius(radius)
        .padding(.bottom, -radius)
    }
}

生成的视图如下所示:

如您所见,其框架与其内容完美对齐(蓝色边框)。可以使用相同的方法对成对的底角或侧角进行倒圆角。希望这有帮助!


13
投票

另一个选择(也许更好)实际上是退回到 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)
    }
}

9
投票

这里是针对 macOS 的适配:

// defines OptionSet, which corners to be rounded – same as UIRectCorner
struct RectCorner: OptionSet {
    
    let rawValue: Int
        
    static let topLeft = RectCorner(rawValue: 1 << 0)
    static let topRight = RectCorner(rawValue: 1 << 1)
    static let bottomRight = RectCorner(rawValue: 1 << 2)
    static let bottomLeft = RectCorner(rawValue: 1 << 3)
    
    static let allCorners: RectCorner = [.topLeft, topRight, .bottomLeft, .bottomRight]
}


// draws shape with specified rounded corners applying corner radius
struct RoundedCornersShape: Shape {
    
    var radius: CGFloat = .zero
    var corners: RectCorner = .allCorners

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

        let p1 = CGPoint(x: rect.minX, y: corners.contains(.topLeft) ? rect.minY + radius  : rect.minY )
        let p2 = CGPoint(x: corners.contains(.topLeft) ? rect.minX + radius : rect.minX, y: rect.minY )

        let p3 = CGPoint(x: corners.contains(.topRight) ? rect.maxX - radius : rect.maxX, y: rect.minY )
        let p4 = CGPoint(x: rect.maxX, y: corners.contains(.topRight) ? rect.minY + radius  : rect.minY )

        let p5 = CGPoint(x: rect.maxX, y: corners.contains(.bottomRight) ? rect.maxY - radius : rect.maxY )
        let p6 = CGPoint(x: corners.contains(.bottomRight) ? rect.maxX - radius : rect.maxX, y: rect.maxY )

        let p7 = CGPoint(x: corners.contains(.bottomLeft) ? rect.minX + radius : rect.minX, y: rect.maxY )
        let p8 = CGPoint(x: rect.minX, y: corners.contains(.bottomLeft) ? rect.maxY - radius : rect.maxY )

        
        path.move(to: p1)
        path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY),
                    tangent2End: p2,
                    radius: radius)
        path.addLine(to: p3)
        path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY),
                    tangent2End: p4,
                    radius: radius)
        path.addLine(to: p5)
        path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY),
                    tangent2End: p6,
                    radius: radius)
        path.addLine(to: p7)
        path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY),
                    tangent2End: p8,
                    radius: radius)
        path.closeSubpath()

        return path
    }
}

// View extension, to be used like modifier:
// SomeView().roundedCorners(radius: 20, corners: [.topLeft, .bottomRight])
extension View {
    func roundedCorners(radius: CGFloat, corners: RectCorner) -> some View {
        clipShape( RoundedCornersShape(radius: radius, corners: corners) )
    }
}

9
投票

iOS 16+

只需使用UnevenRoundedRectangle

 VStack {
    UnevenRoundedRectangle(cornerRadii: .init(bottomTrailing: 50, topTrailing: 50))
                    .fill(.orange)
                    .frame(width: 200, height: 100)
 }

结果:


5
投票

最干净的又一个选项(iOS 15+):

.background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
.background(content: { Color.white.padding(.top, 20) })


1
投票

我想补充Kontiki的答案;

如果您使用选项 2 并且想要向形状添加描边,请务必在返回路径之前添加以下内容:

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

否则,笔画会从左上角到顶边中间断掉。


1
投票

使用此链接中的代码,其中形状类型的结构为 RoundedCorners:Shape{ //} https://stackoverflow.com/a/56763282/10637692

在此链接之前使用以下代码行

path.closeSubpath()

path.move(to: CGPoint(x: 280, y: 20))
path.addLine(to: CGPoint(x: w, y: 0))
path.addArc(center: CGPoint(x: w, y: 70), radius: br,   //x =  move triangle to right left
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180), clockwise: false)

0
投票

达到此目的的当前版本是;

VStack {
            Text("Top Leading & Trailing Corners")
        }.frame(maxWidth: .infinity, maxHeight: 200)
            .background(.white)
            .clipShape(
                .rect(cornerRadii: RectangleCornerRadii(topLeading: 24, topTrailing: 24))
        )
© www.soinside.com 2019 - 2024. All rights reserved.