【问题标题】:Round Specific Corners SwiftUI圆特定角 SwiftUI
【发布时间】:2019-11-07 15:55:16
【问题描述】:

我知道您可以使用.cornerRadius() 来圆化 swiftUI 视图的所有角,但是有没有办法只圆化特定的角,例如顶部?

【问题讨论】:

  • 我最终跳过了 SwiftUI,因为无论我做什么,性能都很糟糕。最后,我最终使用了可表示 UIKit 视图的 CALayer 的 maskedCorners 属性。

标签: user-interface rounding swiftui cornerradius


【解决方案1】:

用作自定义修饰符

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

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

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


示例项目:

【讨论】:

  • 这个解决方案比公认的要干净得多。
  • 结帐this answer自定义边框@SorinLica
  • 你知道这将如何在 macOS(不是 Catalyst)的 SwiftUI 视图中实现吗?看起来NSRect 没有等效的角对象,NSBezierPath 没有byRoundingCorners 参数。
  • 在 ios14 之前工作正常,底部视图消失
  • 它在 iOS14 中不再正常工作,我遇到了一些布局问题。
【解决方案2】:

有两个选项,您可以使用ViewPath,或者您可以创建自定义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
    }
}

【讨论】:

  • 如果您定义了一个自定义的Shape,则不必涉及GeometryReader
  • 只是对选项 2 的一个小修正:我认为路径从错误的 x 值开始,因为它看起来切断了左半部分的顶线。我将路径起点更改为path.move(to: CGPoint(x: tl, y: 0)),这似乎解决了它。
  • 这不像下面的答案那么干净,但是当我想绕 3 个角时,它是唯一一个在 iOS 14 中有效的方法。当我希望它们四舍五入到 .infinity 时,另一种方法最终会四舍五入
  • 虽然在 iOS 上使用 UIBezierPath 效果很好,但它不适用于 macOS 或其他没有 UIKit 的地方。在纯 SwiftUI 中手动绘制路径适用于所有 Apple 平台。
【解决方案3】:

视图修饰符让一切变得简单:

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

【讨论】:

  • 你知道这将如何在 macOS(不是 Catalyst)的 SwiftUI 视图中实现吗?看起来NSRect 没有等效的角对象,NSBezierPath 没有byRoundingCorners 参数。
  • 在 iOS 14 上还有其他人使用这个或以上版本吗?我发现它会将任何滚动视图剪辑到边缘 - 相同的代码在 iOS 13 设备/模拟器上运行良好。
  • 嗨,@RichardGroves,我遇到了和你一样的问题。在这里查看我的答案:stackoverflow.com/a/64571117/4733603
  • @KyleXie 谢谢,但我需要它来处理只有 2 个圆角并且没有标准形状可以做到这一点的情况,这就是为什么我首先要使用自定义路径形状。
  • @RichardGroves,啊,我明白了。我目前使用完整的圆角并使用其他覆盖底角的东西。我知道这确实是黑客行为,但我没有其他方法可以让它发挥作用。
【解决方案4】:

另一个选择(也许更好)实际上是为此退回到 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 是什么?
【解决方案5】:

我想补充 Kontiki 的答案;

如果您使用选项 2 并想为形状添加笔触,请务必在返回路径之前添加以下内容:

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

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

【讨论】:

    猜你喜欢
    • 2022-11-11
    • 2021-06-11
    • 2023-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-31
    相关资源
    最近更新 更多