You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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
    }
}

当前效果:

当前运行效果


修复方案

你当前的实现存在两个核心问题:

  1. CAShapeLayer的描边是沿路径居中绘制的,等边三角形的内缩距离不能直接用半线宽计算,需要结合三角形内角的三角函数值调整
  2. 顶点计算没有以容器中心为基准,导致最终图形和容器中心不重合

修改后的代码如下:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 08:21:01