iOS - 如何在使用自定义 UIBezierPath 绘制的 CAShapeLayer 中设置 CATextLayer?

Posted

技术标签:

【中文标题】iOS - 如何在使用自定义 UIBezierPath 绘制的 CAShapeLayer 中设置 CATextLayer?【英文标题】:iOS - How to set a CATextLayer inside a CAShapeLayer which is drawn with a custom UIBezierPath? 【发布时间】:2020-10-14 07:56:58 【问题描述】:

我想编写一个自定义饼形菜单。在下面的代码中,您会看到我如何创建一个包含两个项目的饼状菜单。我的结构如下:我使用矩形 UIBezierPath 和 CAShapeLayer 作为上下文作为我的圆形背景。在我的圆形背景中,我有一个孩子,内部的小圆圈(也是带有 CAShapeLayer 的 UIBezierPath)。我的圆形背景层的其他孩子是项目,它们也是一个使用自定义 UIBezierPath 的 CAShapeLayer(我绘制我的项目取决于项目的数量(不同程度等))。现在我想在每个项目层内添加一个 CATextLayer(“项目 1”、“项目 2”等)。我的问题是,我不知道如何设置特定项目层的框架以及如何以文本在父项目层内动态添加的方式添加特定的 CATextLayer。在我的情况下,CATextLayer 取决于菜单背景层的框架。

func setMenuBackgroundLayer() 
        //Draw a circle background with UIBezierPath for the static pie menu
        let path = UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2), radius: menuRadius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
    
        menuBackgroundLayer = CAShapeLayer()
        menuBackgroundLayer.path = path.cgPath
        menuBackgroundLayer.fillColor = menuBackgroundLayerColor.cgColor
        menuBackgroundLayer.frame = self.bounds
        menuBackgroundLayer.zPosition = 1
        
        self.layer.addSublayer(menuBackgroundLayer)
        
        //Draw the inner circle (back button)
        let pathInner = UIBezierPath(arcCenter: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2), radius: innerCircleRadius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
        
        innerCircleLayer = CAShapeLayer()
        innerCircleLayer.path = pathInner.cgPath
        innerCircleLayer.fillColor = menuBackgroundLayerColor.cgColor
        innerCircleLayer.strokeColor = UIColor.black.cgColor
        innerCircleLayer.lineWidth = 1
        innerCircleLayer.frame = menuBackgroundLayer.frame
        menuBackgroundLayer.addSublayer(innerCircleLayer)
        //Set the inner circle above all other menu items
        innerCircleLayer.zPosition = 100
        
        //Add the arrow image inside the inner circle
        //addBackImage()
    
func insertMenuItems() 
        //Compare which item has to get inserted and insert it
        if numberOfItems == 1 
            let path = UIBezierPath(arcCenter: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2), radius: menuRadius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
            
            item1Layer = CAShapeLayer()
            item1Layer.path = path.cgPath
            item1Layer.fillColor = menuBackgroundLayerColor.cgColor
            item1Layer.strokeColor = UIColor.black.cgColor
            item1Layer.lineWidth = 1
            item1Layer.frame = menuBackgroundLayer.bounds
            menuBackgroundLayer.addSublayer(item1Layer)
            item1Layer.zPosition = 2
            
            let textLayer = CATextLayer()
            textLayer.string = "ITEM 1"
            textLayer.foregroundColor = UIColor.white.cgColor
            textLayer.font = UIFont(name: "Avenir", size: 15.0)
            textLayer.fontSize = 15.0
            textLayer.alignmentMode = CATextLayerAlignmentMode.center
            textLayer.zPosition = 3
            textLayer.frame = item1Layer.bounds
            textLayer.position = CGPoint(x: item1Layer.position.x, y: item1Layer.position.y + 20.0)
            textLayer.contentsScale = UIScreen.main.scale
            item1Layer.addSublayer(textLayer)
        
        else if numberOfItems == 2 
            //Item 1
            let path1 = UIBezierPath()
            path1.move(to: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2))
            path1.addArc(withCenter: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2), radius: menuRadius, startAngle: rad2deg(180.0), endAngle: rad2deg(0.0), clockwise: true)
            path1.close()
            item1Layer = CAShapeLayer()
            item1Layer.path = path1.cgPath
            item1Layer.fillColor = menuBackgroundLayerColor.cgColor
            item1Layer.strokeColor = UIColor.black.cgColor
            item1Layer.lineWidth = 1
            item1Layer.frame = menuBackgroundLayer.bounds
            menuBackgroundLayer.addSublayer(item1Layer)
            item1Layer.zPosition = 2
            
            let textLayer1 = CATextLayer()
            textLayer1.string = "ITEM 1"
            textLayer1.foregroundColor = UIColor.white.cgColor
            textLayer1.font = UIFont(name: "Avenir", size: 15.0)
            textLayer1.fontSize = 15.0
            textLayer1.alignmentMode = CATextLayerAlignmentMode.center
            textLayer1.zPosition = 3
            textLayer1.frame = item1Layer.bounds
            textLayer1.position = CGPoint(x: item1Layer.position.x, y: item1Layer.position.y + 20.0)
            textLayer1.contentsScale = UIScreen.main.scale
            item1Layer.addSublayer(textLayer1)
            
            //Item 2
            let path2 = UIBezierPath()
            path2.move(to: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2))
            path2.addArc(withCenter: CGPoint(x: menuBackgroundLayer.frame.size.width / 2, y: menuBackgroundLayer.frame.size.height / 2), radius: menuRadius, startAngle: rad2deg(0.0), endAngle: rad2deg(180.0), clockwise: true)
            path2.close()
            item2Layer = CAShapeLayer()
            item2Layer.path = path2.cgPath
            item2Layer.fillColor = menuBackgroundLayerColor.cgColor
            item2Layer.strokeColor = UIColor.black.cgColor
            item2Layer.lineWidth = 1
            item2Layer.frame = menuBackgroundLayer.bounds
            menuBackgroundLayer.addSublayer(item2Layer)
            item2Layer.zPosition = 2
            
            let textLayer2 = CATextLayer()
            textLayer2.string = "ITEM 2"
            textLayer2.foregroundColor = UIColor.white.cgColor
            textLayer2.font = UIFont(name: "Avenir", size: 15.0)
            textLayer2.fontSize = 15.0
            textLayer2.alignmentMode = CATextLayerAlignmentMode.center
            textLayer2.zPosition = 3
            textLayer2.frame = item2Layer.bounds
            textLayer2.position = CGPoint(x: item2Layer.position.x, y: item2Layer.position.y + 20.0)
            textLayer2.contentsScale = UIScreen.main.scale
            item2Layer.addSublayer(textLayer2)
        
and so on...

【问题讨论】:

您能否举例说明您的饼状菜单包含 3 个项目(包括 Item1、Item2、Item3 以及它们之间的所有行)的外观? ***.com/questions/64333630/… @Olha 第一张图片展示了我的想法。所以我想为每个饼片添加一个 CATextLayer 在特定的饼片内 您是否正在尝试做这样的事情? ***.com/a/62269780/6257435 【参考方案1】:

所以,这里有一个粗略的原型,可以满足您的需要,但不是很精确。

如果你想旋转文字,这可以通过CATransform来实现。 你可以在这里玩代码:https://github.com/gatamar/***_answers/tree/master/so64348954

如果这几乎是您所需要的,我可以让它更精确。

饼形菜单的代码:

import Foundation
import UIKit

class HackLinesView: UIView 
    init(frame: CGRect, partsCount parts: Int) 
        super.init(frame: frame)
        backgroundColor = .clear
        
        let side = frame.width/2
        // add lines
        for part in 0..<parts 
            let angle = CGFloat(part)/CGFloat(parts) * 2 * .pi
            let lineLayer = CAShapeLayer()
            lineLayer.backgroundColor = UIColor.black.cgColor
            let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 1, height: side))
            lineLayer.path = path.cgPath
            lineLayer.transform = CATransform3DMakeRotation(angle, 0, 0, 1)
            layer.addSublayer(lineLayer)
        
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    
class PieMenuView: UIView 
    init(frame: CGRect, partsCount parts: Int) 
        assert( abs(frame.width-frame.height) < 0.001)
        super.init(frame: frame)
        setupLayers(parts)
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    private func setupLayers(_ parts: Int) 
        let side = bounds.width
        let outerRadius = side * 0.5
        let innerRadius = side * 0.2
        
        // add outer circle
        let outerCircleLayer = CAShapeLayer()
        outerCircleLayer.frame = bounds
        outerCircleLayer.cornerRadius = outerRadius
        outerCircleLayer.backgroundColor = UIColor.orange.cgColor
        layer.addSublayer(outerCircleLayer)
        
        // add inner circle
        let innerCircleLayer = CAShapeLayer()
        innerCircleLayer.frame = CGRect(x: side/2-innerRadius, y: side/2-innerRadius, width: innerRadius*2, height: innerRadius*2)
        innerCircleLayer.cornerRadius = innerRadius
        innerCircleLayer.backgroundColor = UIColor.yellow.cgColor
        layer.addSublayer(innerCircleLayer)
        
        let linesView = HackLinesView(frame: CGRect(x: side/2, y: side/2, width: side, height: side), partsCount: parts)
        addSubview(linesView)
        
        // add text
        for part in 0..<parts 
            let angle = CGFloat(part)/CGFloat(parts) * 2 * .pi
            
            let textLayer = CATextLayer()
            textLayer.string = String(format: "%d", part)
            textLayer.foregroundColor = UIColor.blue.cgColor
            
            // calc the center for text layer
            let x1 = side/2
            let y1 = side/2
            let x2 = x1 + cos(angle)*outerRadius
            let y2 = y1 + sin(angle)*outerRadius
            
            let textCenterX = (x1 + x2)/2, textCenterY = (y1 + y2)/2
            let textLayerSide: CGFloat = 50
            
            textLayer.frame = CGRect(x: textCenterX-textLayerSide/2, y: textCenterY-textLayerSide/2, width: textLayerSide, height: textLayerSide)
            
            layer.addSublayer(textLayer)
        
    


【讨论】:

我做的有点不同(我计算了每个文本项在整个框架内的位置)但非常感谢您的回答:D

以上是关于iOS - 如何在使用自定义 UIBezierPath 绘制的 CAShapeLayer 中设置 CATextLayer?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何在 iOS 应用中使用自定义字体? [复制]

如何在iOS8自定义键盘中使用自动更正和快捷列表?

如何在iOS中实现,将JSValue 转为自定义类型

如何在 iOS 中管理自定义单元格标签值?

如何在iOS7中使用具有动态文本大小的自定义字体

如何在 iOS 应用程序中使用自定义字体? [复制]