CATextLayer的位置和变换问题

问题描述 投票:0回答:1

[我正在尝试构建一个控件,如附加的圆形图像,其中每个部分的间距均相等。段数可以根据提供的数组进行更改。

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

enter image description here

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)
   }

}

circleLayer添加为superlayer,包含UIView的整个区域。我的要求是在带有角度的形状内垂直和水平居中添加文本。我遇到的问题是在角度良好的情况下在形状内居中放置文本。

谢谢

编辑:如果删除textLayer旋转代码,则它看起来像此图像。enter image description here

ios swift cashapelayer quartz-core catextlayer
1个回答
0
投票

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

enter image description here

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

所以,首先我们计算圆:

enter image description here

然后将每个角度一分为二并找到圆上的点:

enter image description here

然后计算标签的边框(我使用radius * 0.6的最大宽度),将该框架的中心放在圆上的点上,然后旋转文本层:

enter image description here

以及结果,没有“指南”:

enter image description here

注意:对于这些图像,我将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),
        ])

    }

}

以上代码中使用的“ 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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.