如何在 CAShapLayer 中设置正确的三角形路径和笔划?

How to set correct path for triangle along with stroke in CAShapLayer?

我必须创建带有适合另一个视图的边框的三角形。但问题是,一旦我设置了描边,三角形就会比容器大。所以,我决定计算内三角大小

应用边框后,我们将有两个三角形,如 Homothetic Transformation。我只想计算小三角形的大小。因此,边框将适合容器。

我试图设置它,但我无法为三角形完成它。因为内三角中心不在容器中心。

Please note that this is first try with equilateral Triangle. Originally I want solve with same solution with any shape of Polygons.

我附上完整的 demo xcode project at here.

请帮我解决这个问题。谢谢...

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(){
 
        // Homothetic Transformation
        // https://math.stackexchange.com/questions/3952129/calculate-bounds-of-the-inner-rectangle-of-the-polygon-based-on-its-constant-bo
        // 
        
         
        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
    }
    
     
}

当前输出:

请注意,您链接的 Math.SE post 中内三角形边界坐标的表达式在这里不是很有用,因为 post 假设 等边 三角形。但是,您的三角形不是等边的。它的底部长度与其高度相同。它所在的视图毕竟是正方形。

在做了一些三角函数之后,我发现内三角形边界矩形的顶部被 lineWidth 除以 sin(atan(0.5)) 插入。显然,底部由 lineWidth 插入。并且由于内部边界矩形也必须是正方形,左右插图都是顶部和底部插图之和的一半。

这是一张 GeoGebra 屏幕截图,说明了 sin(atan(0.5)) 的来源(我对 GeoGebra 相当不满意,所以请原谅我的混乱):

外三角形右半边的底边是它高的一半,所以 beta = atan(0.5)。然后考虑三角形 E'EA2,注意 sin(beta) = lineWidth / EE',EE' 是我们想要的顶部插图。

// makeTriangle

let lineWidth: CGFloat = borderWidth / 2
let top = lineWidth / sin(atan(0.5))
let bottom = lineWidth
let horizontal = (top + bottom) / 2
let rect = bounds.inset(by:
            UIEdgeInsets(
                top: top,
                left: horizontal,
                bottom: bottom,
                right: horizontal
            )
)

let path = CGMutablePath()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.closeSubpath()()

self.path = path

现在外三角形应该整齐地放在正方形内。但请注意,默认情况下,CALayer 属性 变化是动画的,如果滑动滑块太快,外三角形可能会超出正方形的边界,动画跟不上。您可以通过将设置 CALayer 属性的代码包装到 CATransactionsetDisableActions(true) 中来禁用动画。例如在 borderWidth 的 setter 中:

set{

    CATransaction.begin()
    CATransaction.setDisableActions(true)
    
    shapeLayer.lineWidth = newValue
     
    makeTriangle()
    CATransaction.commit()
}