CATextLayer位置和变换问题

Sur*_*eet 0 cashapelayer ios catextlayer quartz-core swift

我正在尝试构建一个类似附加圆形图像的控件,其中多个段的每个部分的空间相等。段数可以根据提供的数组而改变。

到目前为止,我已经使用 CAShapeLayer 和 UIBezierPath 开发了这个。还在每个形状图层的中心添加了文本。到目前为止,我已经添加了用于生成此控件的代码。

在此输入图像描述

let circleLayer = CALayer()
func createCircle(_ titleArray:[String]) {
     update(bounds: bounds, titleArray: titleArray)
     containerView.layer.addSublayer(circleRenderer.circleLayer)
}


 func update(bounds: CGRect, titleArray: [String]) {
        let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
        circleLayer.position = position
        circleLayer.bounds = bounds
        update(titleArray: titles)
    }

func update(titleArray: [String]) {
   let center = CGPoint(x: circleLayer.bounds.size.width / 2.0, y: circleLayer.bounds.size.height / 2.0)
   let radius:CGFloat = min(circleLayer.bounds.size.width, circleLayer.bounds.size.height) / 2
   let segmentSize = CGFloat((Double.pi*2) / Double(titleArray.count))
   
   for i in 0..<titleArray.count {
       let startAngle = segmentSize*CGFloat(i) - segmentSize/2
       let endAngle = segmentSize*CGFloat(i+1) - segmentSize/2
       let midAngle = (startAngle+endAngle)/2
       
       let shapeLayer = CAShapeLayer()
       shapeLayer.fillColor = UIColor.random.cgColor
       
       let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
       bezierPath.addLine(to: center)
       shapeLayer.path = bezierPath.cgPath
       
       let height = titleArray[i].height(withConstrainedWidth: radius-20, font: UIFont.systemFont(ofSize: 15))
       let frame = shapeLayer.path?.boundingBoxOfPath ?? CGRect.zero
       let textLayer = TextLayer()
       textLayer.frame = CGRect(x: 0, y: 0, width: 70, height: height)
       textLayer.position = CGPoint(x: frame.center.x, y: frame.center.y)
       textLayer.fontSize = 15
       textLayer.contentsScale = UIScreen.main.scale
       textLayer.alignmentMode = .center
       textLayer.string = titleArray[i]
       textLayer.isWrapped = true
       textLayer.backgroundColor = UIColor.black.cgColor
       textLayer.foregroundColor = UIColor.white.cgColor
       textLayer.transform = CATransform3DMakeRotation(midAngle, 0.0, 0.0, 1.0)
       shapeLayer.addSublayer(textLayer)
       circleLayer.addSublayer(shapeLayer)
   }
Run Code Online (Sandbox Code Playgroud)

}

circleLayer添加为superlayer,包含 UIView 的整个区域。 我的要求是在带有角度的形状内添加垂直和水平居中的文本。我面临着在形状内居中文本而角度很好的问题。

谢谢

编辑: 如果我删除 textLayer 旋转代码,那么它看起来像这个图像。 在此输入图像描述

Don*_*Mag 5

您的标签不是“居中”的,因为您使用的是楔形边界框的几何中心:

在此输入图像描述

您需要做的是计算一个“内”圆,其半径为整个半径的 1/2,然后找到该圆上的点来放置标签。

所以,首先我们计算圆:

在此输入图像描述

然后平分每个角并找到圆上的点:

在此输入图像描述

然后计算标签的边界框(我使用了 max-width radius * 0.6),将该框架的中心放在圆上的点上,然后旋转文本层:

在此输入图像描述

结果,没有“指南”:

在此输入图像描述

注意:对于这些图像,我使用radius * 0.55- 或者只是比半径的 1/2 稍微远一些 - 作为“内圆”。这让我的外观稍微好一些,因为当我们到达圆的中心时楔形变窄。将其更改为radius * 0.6可能看起来更好。

这是生成此视图的代码:

struct Wedge {
    var color: UIColor = .cyan
    var label: String = ""
}

class WedgeView: UIView {

    var wedges: [Wedge] = []

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {

    }

    override func layoutSubviews() {
        super.layoutSubviews()

        layer.sublayers?.forEach { $0.removeFromSuperlayer() }

        setup()

    }

    func setup() -> Void {

        // initialize local variables
        var startAngle: CGFloat = 0

        var outerRadius: CGFloat = 0.0
        var halfRadius: CGFloat = 0.0

        // initialize local constants
        let viewCenter: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)
        let diameter = bounds.width

        let fontHeight: CGFloat = ceil(12.0 * (bounds.height / 300.0))
        let textLayerFont = UIFont.systemFont(ofSize: fontHeight, weight: .light)

        outerRadius = diameter * 0.5
        halfRadius = outerRadius * 0.55

        let labelMaxWidth:CGFloat = outerRadius * 0.6

        startAngle = -(.pi * (1.0 / CGFloat(wedges.count)))

        for i in 0..<wedges.count {
            let endAngle = startAngle + 2 * .pi * (1.0 / CGFloat(wedges.count))
            let shape = CAShapeLayer()
            let path: UIBezierPath = UIBezierPath()
            path.addArc(withCenter: viewCenter, radius: outerRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
            path.addLine(to: viewCenter)
            path.close()
            shape.path = path.cgPath

            shape.fillColor = wedges[i].color.cgColor
            shape.strokeColor = UIColor.black.cgColor

            self.layer.addSublayer(shape)

            let textLayer = CATextLayer()

            textLayer.font = textLayerFont
            textLayer.fontSize = fontHeight
            let string = wedges[i].label
            textLayer.string = string

            textLayer.foregroundColor = UIColor.white.cgColor
            textLayer.backgroundColor = UIColor.black.cgColor

            textLayer.isWrapped = true
            textLayer.alignmentMode = CATextLayerAlignmentMode.center
            textLayer.contentsScale = UIScreen.main.scale

            let bisectAngle = startAngle + ((endAngle - startAngle) * 0.5)
            let p = CGPoint.pointOnCircle(center: viewCenter, radius: halfRadius, angle: bisectAngle)

            var textLayerframe = CGRect(x: 0, y: 0, width: labelMaxWidth, height: 0)
            let h = string.getLableHeight(labelMaxWidth, usingFont: textLayerFont)
            textLayerframe.size.height = h

            textLayerframe.origin.x = p.x - (textLayerframe.size.width * 0.5)
            textLayerframe.origin.y = p.y - (textLayerframe.size.height * 0.5)

            textLayer.frame = textLayerframe

            self.layer.addSublayer(textLayer)

            textLayer.transform = CATransform3DMakeRotation(bisectAngle, 0.0, 0.0, 1.0)

            // uncomment this block to show the dashed-lines
            /*
            let biLayer = CAShapeLayer()
            let dash = UIBezierPath()
            dash.move(to: viewCenter)
            dash.addLine(to: p)
            biLayer.strokeColor = UIColor.yellow.cgColor
            biLayer.lineDashPattern = [4, 4]
            biLayer.path = dash.cgPath
            self.layer.addSublayer(biLayer)
            */

            startAngle = endAngle
        }

        // uncomment this block to show the half-radius circle
        /*
        let tempLayer: CAShapeLayer = CAShapeLayer()
        tempLayer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: outerRadius - halfRadius, dy: outerRadius - halfRadius)).cgPath
        tempLayer.fillColor = UIColor.clear.cgColor
        tempLayer.strokeColor = UIColor.green.cgColor
        tempLayer.lineWidth = 1.0
        self.layer.addSublayer(tempLayer)
        */

    }

}

class WedgesWithRotatedLabelsViewController: UIViewController {

    let wedgeView: WedgeView = WedgeView()

    var wedges: [Wedge] = []

    let colors: [UIColor] = [
        UIColor(red: 1.00, green: 0.00, blue: 0.00, alpha: 1.0),
        UIColor(red: 0.00, green: 0.50, blue: 0.00, alpha: 1.0),
        UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
        UIColor(red: 1.00, green: 0.50, blue: 0.50, alpha: 1.0),
        UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
        UIColor(red: 0.50, green: 0.50, blue: 1.00, alpha: 1.0),
    ]
    let labels: [String] = [
        "This is long text for Label 1",
        "Label 2",
        "Longer Label 3",
        "Label 4",
        "Label 5",
        "Label 6",
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)

        for (c, s) in zip(colors, labels) {
            wedges.append(Wedge(color: c, label: s))
        }

        wedgeView.wedges = wedges

        view.addSubview(wedgeView)
        wedgeView.translatesAutoresizingMaskIntoConstraints = false

        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([
            wedgeView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
            wedgeView.heightAnchor.constraint(equalTo: wedgeView.widthAnchor),
            wedgeView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            wedgeView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])

    }

}
Run Code Online (Sandbox Code Playgroud)

上面的代码中使用了几个“helper”扩展:

// get a random color
extension UIColor {
    static var random: UIColor {
        return UIColor(red: .random(in: 0...1),
                       green: .random(in: 0...1),
                       blue: .random(in: 0...1),
                       alpha: 1.0)
    }
}

// get the point on a circle at specific radian
extension CGPoint {
    static func pointOnCircle(center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint {
        let x = center.x + radius * cos(angle)
        let y = center.y + radius * sin(angle)

        return CGPoint(x: x, y: y)
    }
}

// get height of word-wrapping string with max-width
extension String {
    func getLableHeight(_ forWidth: CGFloat, usingFont: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: forWidth, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: usingFont], context: nil)
        return ceil(boundingBox.height)
    }
}
Run Code Online (Sandbox Code Playgroud)