如何在CAShapeLayer中为带描边的三角形设置适配容器的正确路径
我需要创建一个带边框的三角形,使其恰好适配另一个视图容器。但遇到的问题是,设置描边后三角形尺寸会超出容器,因此我计划计算内接三角形尺寸。
添加描边后会得到两个符合位似变换(Homothetic Transformation)的三角形,我需要计算内部小三角形的尺寸,保证边框完全适配容器。
我已尝试自行实现,但针对三角形的适配一直未成功,原因是内接三角形的中心与容器中心不重合。
请注意,本次先尝试实现等边三角形的适配,我最终希望该方案可同时解决任意多边形的同类问题。
恳请帮忙解决该问题,谢谢。
ShapeView.swift
import UIKit class ShapeView: UIView { var path: CGPath? { didSet{ setNeedsDisplay(bounds) } } var cornerRadius: CGFloat = .zero var borderWidth: CGFloat{ set{ shapeLayer.lineWidth = newValue makeTriangle() } get{ return shapeLayer.lineWidth } } var borderColor: UIColor{ set{ shapeLayer.strokeColor = newValue.cgColor } get{ return UIColor(cgColor: shapeLayer.strokeColor ?? UIColor.clear.cgColor) } } var background: UIColor{ set{ shapeLayer.fillColor = newValue.cgColor setNeedsDisplay(bounds) } get{ return UIColor(cgColor: shapeLayer.fillColor ?? UIColor.clear.cgColor) } } private lazy var shapeLayer: CAShapeLayer = { let shapeLayer = CAShapeLayer() shapeLayer.lineWidth = .zero layer.addSublayer(shapeLayer) return shapeLayer }() func makeTriangle(){ let lineWidth: CGFloat = borderWidth/2 let size = (frame.width)-lineWidth let rect = CGRect(x: lineWidth, y: lineWidth , width:size, height: size) var cornerRadius: CGFloat = self.cornerRadius-lineWidth cornerRadius = max(cornerRadius, 0) let path = CGMutablePath() path.move(to: CGPoint(x: rect.width/2.0, y: lineWidth)) path.addLine(to: CGPoint(x: lineWidth, y: rect.height - lineWidth)) path.addLine(to: CGPoint(x: rect.width - lineWidth, y: rect.height - lineWidth)) path.closeSubpath() self.path = path } override func draw(_ rect: CGRect) { super.draw(rect) guard let path = path else{ return } shapeLayer.path = path } }
当前效果:

修复方案
你当前的实现存在两个核心问题:
- CAShapeLayer的描边是沿路径居中绘制的,等边三角形的内缩距离不能直接用半线宽计算,需要结合三角形内角的三角函数值调整
- 顶点计算没有以容器中心为基准,导致最终图形和容器中心不重合
修改后的代码如下:
1. 替换makeTriangle方法
func makeTriangle(){ let lineWidth = borderWidth // 等边三角形常量参数 let sin60 = CGFloat(sqrt(3)) / 2 // 以bounds为计算基准,避免父视图布局影响 let containerWidth = bounds.width let center = CGPoint(x: bounds.midX, y: bounds.midY) // 计算内接三角形的边长:原最大可用边长减去两倍线宽对应的缩进量 let maxAvailableSide = containerWidth - lineWidth let indent = lineWidth / sin60 let innerSide = maxAvailableSide - 2 * indent let innerHeight = innerSide * sin60 // 基于中心计算三个顶点坐标 let topPoint = CGPoint(x: center.x, y: center.y - innerHeight * 2 / 3) let leftPoint = CGPoint(x: center.x - innerSide / 2, y: center.y + innerHeight / 3) let rightPoint = CGPoint(x: center.x + innerSide / 2, y: center.y + innerHeight / 3) let path = CGMutablePath() path.move(to: topPoint) path.addLine(to: leftPoint) path.addLine(to: rightPoint) path.closeSubpath() self.path = path }
2. 添加布局更新适配
新增layoutSubviews方法,避免视图尺寸变化时图形错位:
override func layoutSubviews() { super.layoutSubviews() shapeLayer.frame = bounds makeTriangle() }
如果后续需要扩展到任意多边形,通用的位似变换实现逻辑为:
- 计算原始多边形的重心作为位似中心
- 对每个顶点,沿顶点指向重心的方向,缩进
lineWidth / sin(内角/2)的距离得到新顶点 - 用所有新顶点生成路径即可
内容的提问来源于stack exchange,提问作者iDeveloper
相关产品推荐
相关产品推荐

