SwiftUI中Path绘制的自定义Shape无法正确填充问题求助
SwiftUI自定义Shape填充异常修复
我在SwiftUI中使用Path绘制了自定义Shape(HalfCirclesSidesLine),该Shape的描边(stroke)显示正常,但填充(fill)时无法达到预期效果。预览中描边表现正常,但填充结果异常,预期效果与当前效果可参考附图。
问题代码
struct HalfCirclesSidesLine: Shape { var circleDiameter: CGFloat = 10 let distanceBetweenCircles: CGFloat = 10 let cornerDistance: CGFloat = 22 func path(in rect: CGRect) -> Path { var path = Path() // Drawing left side path = drawSide(path: path, rect: rect, isLeftSide: true) // Drawing right side path = drawSide(path: path, rect: rect, isLeftSide: false) path = drawCorners(on: path, rect: rect, radius: circleDiameter / 2) path = drawHorizontalLines(on: path, rect: rect, cornerDistance: cornerDistance) return path } func drawSide(path: Path, rect: CGRect, isLeftSide: Bool) -> Path { var newPath = path let lengthY = rect.height let circleCount = ((lengthY - cornerDistance * 2) / (circleDiameter + distanceBetweenCircles)).rounded(.towardZero) let radius = circleDiameter / 2 let totalHeightForOneElement = distanceBetweenCircles + circleDiameter for i in 0..<Int(circleCount) { let th = totalHeightForOneElement * CGFloat(i) let xPosition = isLeftSide ? rect.minX : rect.maxX newPath.move(to: .init(x: xPosition, y: rect.minY + cornerDistance + th)) newPath.addLine(to: .init(x: xPosition, y: rect.minY + distanceBetweenCircles + cornerDistance + th)) let arcCenter = CGPoint(x: xPosition, y: rect.minY + distanceBetweenCircles + cornerDistance + radius + th) newPath.move(to: .init(x: xPosition, y: rect.minY + distanceBetweenCircles + cornerDistance + radius * 2 + th)) newPath.addArc(center: arcCenter, radius: radius, startAngle: .degrees(90), endAngle: .degrees(-90), clockwise: isLeftSide) } newPath.move(to: .init(x: isLeftSide ? rect.minX : rect.maxX, y: rect.minY + cornerDistance + totalHeightForOneElement * circleCount)) newPath.addLine(to: .init(x: isLeftSide ? rect.minX : rect.maxX, y: rect.maxY - cornerDistance)) if isLeftSide { newPath.move(to: .init(x: rect.minX + cornerDistance, y: rect.maxY)) newPath.addArc(center: .init(x: rect.minX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(0), endAngle: .degrees(270), clockwise: true) } else { newPath.move(to: .init(x: rect.maxX - cornerDistance, y: rect.maxY)) newPath.addArc(center: .init(x: rect.maxX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(180), endAngle: .degrees(-90), clockwise: false) } return newPath } func drawCorners(on path: Path, rect: CGRect, radius: CGFloat) -> Path { var newPath = path // bottom left newPath.move(to: .init(x: rect.minX + cornerDistance, y: rect.maxY)) newPath.addArc(center: .init(x: rect.minX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(0), endAngle: .degrees(270), clockwise: true) // top left newPath.move(to: .init(x: rect.minX + cornerDistance, y: rect.minY)) newPath.addArc(center: .init(x: rect.minX, y: rect.minY), radius: cornerDistance, startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false) // bottom right newPath.move(to: .init(x: rect.maxX - cornerDistance, y: rect.maxY)) newPath.addArc(center: .init(x: rect.maxX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(180), endAngle: .degrees(-90), clockwise: false) // top right newPath.move(to: .init(x: rect.maxX - cornerDistance, y: rect.minY)) newPath.addArc(center: .init(x: rect.maxX, y: rect.minY), radius: cornerDistance, startAngle: .degrees(180), endAngle: .degrees(90), clockwise: true) return newPath } func drawHorizontalLines(on path: Path, rect: CGRect, cornerDistance: CGFloat) -> Path { var newPath = path // Line between top corners newPath.move(to: CGPoint(x: rect.minX + cornerDistance, y: rect.minY)) newPath.addLine(to: CGPoint(x: rect.maxX - cornerDistance, y: rect.minY)) // Line between bottom corners newPath.move(to: CGPoint(x: rect.minX + cornerDistance, y: rect.maxY)) newPath.addLine(to: CGPoint(x: rect.maxX - cornerDistance, y: rect.maxY)) return newPath } } struct DashedCircleLine_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 40) { HalfCirclesSidesLine().fill(Color.red) .frame(width: 200, height: 200) HalfCirclesSidesLine() .stroke(lineWidth: 1) .frame(width: 200, height: 200) } } }
问题原因
当前代码的核心问题是:使用了大量move(to:)指令,将整个图形拆分成了多个独立的子路径。描边可以正常渲染所有子路径,但填充操作需要识别连续闭合的路径区域,分散的子路径会导致填充逻辑错误,只会填充那些意外形成闭合的小区域,而非预期的整体区域。
修复方案
重构Path的构建逻辑,确保整个图形是单一的连续闭合路径,去掉不必要的move(to:),让线条、半圆、角落圆弧连贯衔接,形成完整的封闭回路。
修改后的完整代码
struct HalfCirclesSidesLine: Shape { var circleDiameter: CGFloat = 10 let distanceBetweenCircles: CGFloat = 10 let cornerDistance: CGFloat = 22 func path(in rect: CGRect) -> Path { var path = Path() // 从左上角角落开始,构建连续路径 path.move(to: CGPoint(x: rect.minX + cornerDistance, y: rect.minY)) // 绘制顶部水平线 path.addLine(to: CGPoint(x: rect.maxX - cornerDistance, y: rect.minY)) // 绘制右上角圆弧 path.addArc(center: CGPoint(x: rect.maxX, y: rect.minY), radius: cornerDistance, startAngle: .degrees(180), endAngle: .degrees(90), clockwise: true) // 绘制右侧边(含半圆) path = drawRightSide(path: path, rect: rect) // 绘制右下角圆弧 path.addArc(center: CGPoint(x: rect.maxX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(90), endAngle: .degrees(0), clockwise: true) // 绘制底部水平线 path.addLine(to: CGPoint(x: rect.minX + cornerDistance, y: rect.maxY)) // 绘制左下角圆弧 path.addArc(center: CGPoint(x: rect.minX, y: rect.maxY), radius: cornerDistance, startAngle: .degrees(0), endAngle: .degrees(270), clockwise: true) // 绘制左侧边(含半圆) path = drawLeftSide(path: path, rect: rect) // 闭合路径回到起点 path.closeSubpath() return path } private func drawRightSide(path: Path, rect: CGRect) -> Path { var newPath = path let radius = circleDiameter / 2 let totalHeightPerElement = circleDiameter + distanceBetweenCircles let availableHeight = rect.height - cornerDistance * 2 let circleCount = Int((availableHeight / totalHeightPerElement).rounded(.towardZero)) // 从右上角圆弧终点开始 var currentY = rect.minY + cornerDistance for _ in 0..<circleCount { // 绘制垂直线段 newPath.addLine(to: CGPoint(x: rect.maxX, y: currentY)) currentY += distanceBetweenCircles // 绘制半圆(向右凸) let arcCenter = CGPoint(x: rect.maxX, y: currentY + radius) newPath.addArc(center: arcCenter, radius: radius, startAngle: .degrees(90), endAngle: .degrees(-90), clockwise: false) currentY += circleDiameter } // 绘制最后一段垂直线到右下角圆弧起点 newPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerDistance)) return newPath } private func drawLeftSide(path: Path, rect: CGRect) -> Path { var newPath = path let radius = circleDiameter / 2 let totalHeightPerElement = circleDiameter + distanceBetweenCircles let availableHeight = rect.height - cornerDistance * 2 let circleCount = Int((availableHeight / totalHeightPerElement).rounded(.towardZero)) // 从左下角圆弧终点开始 var currentY = rect.maxY - cornerDistance for _ in 0..<circleCount { // 绘制垂直线段 newPath.addLine(to: CGPoint(x: rect.minX, y: currentY)) currentY -= distanceBetweenCircles // 绘制半圆(向左凸) let arcCenter = CGPoint(x: rect.minX, y: currentY - radius) newPath.addArc(center: arcCenter, radius: radius, startAngle: .degrees(-90), endAngle: .degrees(90), clockwise: false) currentY -= circleDiameter } // 绘制最后一段垂直线到左上角起点 newPath.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerDistance)) return newPath } } struct DashedCircleLine_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 40) { HalfCirclesSidesLine().fill(Color.red) .frame(width: 200, height: 200) HalfCirclesSidesLine() .stroke(lineWidth: 1) .frame(width: 200, height: 200) } } }
关键修改说明
- 连续路径构建:从左上角起点开始,依次绘制顶部水平线、右上角圆弧、右侧边(含半圆)、右下角圆弧、底部水平线、左下角圆弧、左侧边(含半圆),最后闭合路径,形成完整的封闭回路。
- 移除多余
move(to:):所有绘制操作都基于当前路径的终点,不再随意跳转位置,保证路径连贯性。 - 侧边半圆逻辑调整:左右侧边的半圆绘制时,直接衔接前后的垂直线段,无需额外移动路径起点。
内容的提问来源于stack exchange,提问作者PaFi
相关产品推荐
相关产品推荐

