特定圆角

我知道你可以使用 .cornerRadius()来圆一个 Swift UI 视图的所有角落,但是有没有办法只圆一些特定的角落,比如顶部?

74660 次浏览

有两个选项,可以使用 ViewPath,也可以创建自定义 Shape。在这两种情况下,您都可以单独使用它们,或者在 .background(RoundedCorders(...))中使用它们

enter image description here

选项1: 使用路径 + 几何阅读器

(更多关于几何阅读器的信息: 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
}
}

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

用作自定义修饰符

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

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

演示

Demo Image

您需要像下面这样在 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)
}
}

您还可以直接将该形状用作剪切掩码。


样本项目:

Sample

查看修饰符 使其变得简单:

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

例如:

enter image description here

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


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

我想补充一下 Kontiki 的回答;

如果您正在使用选项2,并希望向形状添加笔画,请确保在返回路径之前正确添加以下内容:

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

否则,行程将从左上角中断到顶部的中间。

下面是对 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) )
}
}

第一步

创建一个可以剪辑视图的形状。我们将使用 UIBezierPath来实现特定的圆角。然后将 cgPath复制到 Path

//step 1 -- Create a shape view which can give shape


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

第二步

嵌入形状到 ViewModifier:

//step 2 - embed shape in viewModifier to help use with ease


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

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

第三步

添加签名为 cornerRadius的多态函数:

//step 3 - create a polymorphic view with same name as swiftUI's cornerRadius


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

第四步

使用如下:

//use any way you want
struct ContentView: View {
var body: some View {
VStack {
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(radius: 20.0, corners: [.topLeft])
            

Rectangle()
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(radius: 20.0, corners: [.topLeft, .bottomLeft])
            

Rectangle()
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(radius: 20.0, corners: [.allCorners])
}
}
}

搞定

我已经发现了一个简单的方法来圆一边的角落。它使用了一种“积极-消极填充舞蹈”来完成我正在寻找的东西。

所以它基本上是这样工作的:

  1. 在视图底部添加一些填充
  2. .cornerRadius(_:)绕过所有角落
  3. 通过应用相同值的负填充来删除填充
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)
}
}

由此产生的视图如下:

enter image description here

如您所见,它的框架与其内容(蓝色边框)完全对齐。同样的方法可用于圆对底部或侧角。希望这对谁有帮助!