如何在SwiftUI中把这个Shape的平滑曲线边缘连接起来?
我有一个波浪形状的印章
struct WavyPostageStampShape: Shape {
var amplitude: CGFloat = 8
var wavesX: Int = 6
var wavesY: Int = 10
var phase: CGFloat = 0
var step: CGFloat = 2
func path(in rect: CGRect) -> Path {
var path = Path()
let w = rect.width
let h = rect.height
func wave(_ t: CGFloat, waves: Int) -> CGFloat {
let angle = phase + t * CGFloat(waves) * 2 * .pi
return sin(angle)
}
// MARK: - TOP
var topPoints: [CGPoint] = []
for x in stride(from: rect.minX, through: rect.maxX, by: step) {
let t = (x - rect.minX) / w
let offset = wave(t, waves: wavesX) * amplitude
topPoints.append(CGPoint(x: x, y: rect.minY - offset))
}
// MARK: - RIGHT
var rightPoints: [CGPoint] = []
for y in stride(from: rect.minY, through: rect.maxY, by: step) {
let t = (y - rect.minY) / h
let offset = wave(t, waves: wavesY) * amplitude
rightPoints.append(CGPoint(x: rect.maxX + offset, y: y))
}
// MARK: - BOTTOM
var bottomPoints: [CGPoint] = []
for x in stride(from: rect.maxX, through: rect.minX, by: -step) {
let t = (rect.maxX - x) / w
let offset = wave(t, waves: wavesX) * amplitude
bottomPoints.append(CGPoint(x: x, y: rect.maxY + offset))
}
// MARK: - LEFT
var leftPoints: [CGPoint] = []
for y in stride(from: rect.maxY, through: rect.minY, by: -step) {
let t = (rect.maxY - y) / h
let offset = wave(t, waves: wavesY) * amplitude
leftPoints.append(CGPoint(x: rect.minX - offset, y: y))
}
// MARK: - PATH BUILDING
guard let first = topPoints.first else { return path }
path.move(to: first)
// TOP
for p in topPoints.dropFirst() {
path.addLine(to: p)
}
// TOP-RIGHT corner (quad)
if let lastTop = topPoints.last,
let firstRight = rightPoints.first {
let corner = CGPoint(x: rect.maxX, y: rect.minY)
path.addQuadCurve(to: firstRight, control: corner)
}
// RIGHT
for p in rightPoints.dropFirst() {
path.addLine(to: p)
}
// RIGHT-BOTTOM corner
if let lastRight = rightPoints.last,
let firstBottom = bottomPoints.first {
let corner = CGPoint(x: rect.maxX, y: rect.maxY)
path.addQuadCurve(to: firstBottom, control: corner)
}
// BOTTOM
for p in bottomPoints.dropFirst() {
path.addLine(to: p)
}
// BOTTOM-LEFT corner
if let lastBottom = bottomPoints.last,
let firstLeft = leftPoints.first {
let corner = CGPoint(x: rect.minX, y: rect.maxY)
path.addQuadCurve(to: firstLeft, control: corner)
}
// LEFT
for p in leftPoints.dropFirst() {
path.addLine(to: p)
}
// LEFT-TOP corner
if let lastLeft = leftPoints.last {
let corner = CGPoint(x: rect.minX, y: rect.minY)
path.addQuadCurve(to: first, control: corner)
}
path.closeSubpath()
return path
}
}
示例:
#Preview {
WavyPostageStampShape(
amplitude: 2,
wavesX: 6,
wavesY: 8,
phase: Double.pi / 2
)
.stroke(Color.blue, lineWidth: 2)
.frame(width: 120, height: 200)
}
结果:

我想要这个:

解决方案
我认为这个形状的主要问题是你没有为角落留出空间。
以顶部的波形为例,波形起点的x 坐标需要从 rect.minX 向内缩进,以给角落留出空间。
调整后的数值可以按如下方式计算:
let cornerRadius = 2 * amplitude
let adjustedMinX = rect.minX + cornerRadius
let adjustedMaxX = rect.maxX - cornerRadius
let adjustedMinY = rect.minY + cornerRadius
let adjustedMaxY = rect.maxY - cornerRadius
对w和h的计算需要相应更新:
let w = rect.width - (2 * cornerRadius)
let h = rect.height - (2 * cornerRadius)
顶部沿线波形的区块随之改为如下所示:
// MARK: - TOP
var topPoints: [CGPoint] = []
for x in stride(from: adjustedMinX, through: adjustedMaxX, by: step) {
let t = (x - adjustedMinX) / w
let offset = wave(t, waves: wavesX) * amplitude
topPoints.append(CGPoint(x: x, y: rect.minY - offset))
}
等等。
这几乎解决了问题,但角落仍然不是很圆润。
调整角落的控制点会有帮助。例如:
// LEFT-TOP corner
let corner = CGPoint(x: rect.minX - (cornerRadius / 2), y: rect.minY - (cornerRadius / 2))
path.addQuadCurve(to: first, control: corner)
顺便说一句,你对可选项也有一些冗余的 if let 检查,导致了警告,这些检查可以直接删除。
这是应用了所有修改后的更新形状:
struct WavyPostageStampShape: Shape {
var amplitude: CGFloat = 8
var wavesX: Int = 6
var wavesY: Int = 10
var phase: CGFloat = 0
var step: CGFloat = 2
func path(in rect: CGRect) -> Path {
var path = Path()
let cornerRadius = 2 * amplitude
let adjustedMinX = rect.minX + cornerRadius
let adjustedMaxX = rect.maxX - cornerRadius
let adjustedMinY = rect.minY + cornerRadius
let adjustedMaxY = rect.maxY - cornerRadius
let w = rect.width - (2 * cornerRadius)
let h = rect.height - (2 * cornerRadius)
func wave(_ t: CGFloat, waves: Int) -> CGFloat {
let angle = phase + t * CGFloat(waves) * 2 * .pi
return sin(angle)
}
// MARK: - TOP
var topPoints: [CGPoint] = []
for x in stride(from: adjustedMinX, through: adjustedMaxX, by: step) {
let t = (x - adjustedMinX) / w
let offset = wave(t, waves: wavesX) * amplitude
topPoints.append(CGPoint(x: x, y: rect.minY - offset))
}
// MARK: - RIGHT
var rightPoints: [CGPoint] = []
for y in stride(from: adjustedMinY, through: adjustedMaxY, by: step) {
let t = (y - adjustedMinY) / h
let offset = wave(t, waves: wavesY) * amplitude
rightPoints.append(CGPoint(x: rect.maxX + offset, y: y))
}
// MARK: - BOTTOM
var bottomPoints: [CGPoint] = []
for x in stride(from: adjustedMaxX, through: adjustedMinX, by: -step) {
let t = (adjustedMaxX - x) / w
let offset = wave(t, waves: wavesX) * amplitude
bottomPoints.append(CGPoint(x: x, y: rect.maxY + offset))
}
// MARK: - LEFT
var leftPoints: [CGPoint] = []
for y in stride(from: adjustedMaxY, through: adjustedMinY, by: -step) {
let t = (adjustedMaxY - y) / h
let offset = wave(t, waves: wavesY) * amplitude
leftPoints.append(CGPoint(x: rect.minX - offset, y: y))
}
// MARK: - PATH BUILDING
guard let first = topPoints.first else { return path }
path.move(to: first)
// TOP
for p in topPoints.dropFirst() {
path.addLine(to: p)
}
// TOP-RIGHT corner (quad)
if let firstRight = rightPoints.first {
let corner = CGPoint(x: rect.maxX + (cornerRadius / 2), y: rect.minY - (cornerRadius / 2))
path.addQuadCurve(to: firstRight, control: corner)
}
// RIGHT
for p in rightPoints.dropFirst() {
path.addLine(to: p)
}
// RIGHT-BOTTOM corner
if let firstBottom = bottomPoints.first {
let corner = CGPoint(x: rect.maxX + (cornerRadius / 2), y: rect.maxY + (cornerRadius / 2))
path.addQuadCurve(to: firstBottom, control: corner)
}
// BOTTOM
for p in bottomPoints.dropFirst() {
path.addLine(to: p)
}
// BOTTOM-LEFT corner
if let firstLeft = leftPoints.first {
let corner = CGPoint(x: rect.minX - (cornerRadius / 2), y: rect.maxY + (cornerRadius / 2))
path.addQuadCurve(to: firstLeft, control: corner)
}
// LEFT
for p in leftPoints.dropFirst() {
path.addLine(to: p)
}
// LEFT-TOP corner
let corner = CGPoint(x: rect.minX - (cornerRadius / 2), y: rect.minY - (cornerRadius / 2))
path.addQuadCurve(to: first, control: corner)
path.closeSubpath()
return path
}
}
演示示例现在看起来是这样的:

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。