我正在使用SwiftUI,需要实现模仿特定设计的自定义Shape.形状是长方形,但有明显的特征:上角是圆角的,下边缘类似于一排圆角三角形,看起来就像撕破的纸.

reference

有人可以指导我如何创建这个特定的形状,或者建议一种更好的方法来实现SwiftUI中矩形底部边缘的撕纸效果?

以下是我到目前为止try 过的:

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

        let width = rect.size.width
        let height = rect.size.height

        let bottomLeft = CGPoint(x: 0, y: height)
        let bottomRight = CGPoint(x: width, y: height)
        let topLeft = CGPoint(x: 0, y: 0)
        let topRight = CGPoint(x: width, y: 0)

        path.move(to: bottomRight)

        path.addLine(to: CGPoint(x: bottomRight.x, y: topRight.y))

        path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
        
        path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
        
        let triangleWidth: CGFloat = 11
        let triangleHeight: CGFloat = 6

        var x: CGFloat = 0
        while x <= rect.width {
            let startX = x
            let endX = x + triangleWidth
            let midX = (startX + endX) / 2

            path.move(to: CGPoint(x: startX, y: rect.maxY))
            path.addLine(to: CGPoint(x: midX, y: rect.maxY - triangleHeight))
            path.addLine(to: CGPoint(x: endX, y: rect.maxY))

            x += triangleWidth
        }

        return path
    }
}

poop

推荐答案

我将通过顶部拐角的半径、三角形的拐角半径和三角形的大小来参数化该形状.

给定的rect可能不适合这些三角形中的整个.目前尚不清楚在这种情况下形状应该是什么样子,因此我将三角形的宽度视为minimum宽度.实际的三角形可能比这更宽,以便在给定的rect个中容纳整个三角形.

这是代码.主要 idea 是使用addArc(tangent1End:tangent2End:radius:),以便我们画的每条线都有圆角.另请参阅 comments 中的解释.

struct CustomShape: Shape {
    let topCornersRadius: CGFloat
    let trianglesCornerRadius: CGFloat
    let triangleHeight: CGFloat
    let minTriangleWidth: CGFloat
    
    func path(in rect: CGRect) -> Path {
        Path { path in
            // calculate the minimum width of the triangles,
            // such that it is at least minTriangleWidth, and
            // rect.width fits an integer number of such triangles
            let triangleCount = Int(rect.width / minTriangleWidth)
            let actualTriangleWidth = rect.width / CGFloat(triangleCount)
            
            // start from the top left
            path.move(to: .init(x: rect.minX + topCornersRadius, y: rect.minY))
            // draw the rounded corner for the top left corner
            path.addArc(
                tangent1End: rect.origin,
                tangent2End: .init(x: rect.minX, y: rect.minX + topCornersRadius),
                radius: topCornersRadius
            )
            
            // draw the left side of the shape
            var tangent1End = CGPoint(x: rect.minX, y: rect.maxY)
            var tangent2End = tangent1End.applying(.init(translationX: actualTriangleWidth / 2, y: -triangleHeight))
            path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius / 2)
            
            // draw most of the triangles
            // each iteration draws one side of a triangle
            // we don't draw the right side of the last triangle in the loop,
            // because it will have a different tangent2End
            for i in 0..<(2 * triangleCount - 1) {
                tangent1End = tangent2End
                tangent2End = tangent1End.applying(.init(
                    translationX: actualTriangleWidth / 2,
                    y: triangleHeight * (i.isMultiple(of: 2) ? 1 : -1)
                ))
                path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius)
            }
            
            // draw the right side of the last triangle
            path.addArc(tangent1End: tangent2End, tangent2End: .init(x: rect.maxX, y: rect.minY), radius: trianglesCornerRadius / 2)
            
            // draw the right side of the shape
            path.addArc(
                tangent1End: .init(x: rect.maxX, y: rect.minY),
                tangent2End: rect.origin,
                radius: topCornersRadius
            )
            
            // connects the top right corner of the shape to the top left corner
            path.closeSubpath()
        }
    }
}

示例输出:

enter image description here

Swift相关问答推荐

SwiftUI—如何识别单词并获取视觉中的位置

显示第二个操作紧接在另一个操作后的工作表在SwiftUI中不起作用

当计数大于索引时,索引超出范围崩溃

如何在DATE()中进行比较

如何将函数参数(字符串文字)标记为可本地化?

如何将新事例添加到枚举中?

在SwiftUI中绘制形状的路径后填充形状

按数组大小进行类型判断?

PassthroughSubject 的 AsyncPublisher 值属性未生成所有值

如何为等待函数调用添加超时

如何测试可选 struct 的协议一致性

UIButton 配置不更新按钮标题

如何从另一个 swift 文件中调用函数

有什么方法可以快速为泛型参数分配默认值?

Xcode swift ui 错误KeyPathComparator仅在 macOS 12.0 或更高版本中可用

VStack SwiftUI 中的动态内容高度

如何使被拖动的对象成为前景(foreground)对象

AES 加密和解密

iOS/Swift:如何检测 UITextField 上的touch 动作

UIButton 标题左对齐问题 (iOS/Swift)