如何在不同的CALayer之间保持间距

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

我正在尝试学习图表并遇到麻烦

  1. 在切片之间添加一致的空间。
  2. 按顺序开始动画。

原因,我不希望将分隔符作为单独的拱形,而要同时将两个边都倒圆。在圆角重叠的另一层添加分隔符。enter image description here点击时,第二层显示在顶部enter image description here

高度赞赏任何帮助或指针。

import UIKit

import PlaygroundSupport


var str = "Hello, playground"
struct dataItem {
    var color: UIColor
    var percentage: CGFloat
}

typealias pieAngle = (start: CGFloat, end: CGFloat, color: UIColor)

let pieDataToDisplay = [dataItem(color: .red, percentage: 10),
            dataItem(color: .blue, percentage: 20),
            dataItem(color: .green, percentage: 25),
            dataItem(color: .yellow, percentage: 25),
            dataItem(color: .orange, percentage: 10)]

class USBCircleChart: UIView {

    private var piesToDisplay: [dataItem] = [] { didSet { setNeedsLayout() } }

    private var seperatorSpace: Double = 2.0 { didSet { setNeedsLayout() } }

    func fillDataForChart(with items: [dataItem] )  {
        self.piesToDisplay.append(contentsOf: items)
        print("getting data \(self.piesToDisplay)")
        layoutIfNeeded()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        guard piesToDisplay.count > 0 else { return  }

        print("laying out data")

        let angles = calcualteStartAndEndAngle(items: piesToDisplay)

        for i in angles {
            var dataItem = i
            addSpace(data: &dataItem)
            addShapeToCircle(data: dataItem)
        }

    }

    func addSpace(data:inout pieAngle) -> pieAngle {
        // If space is not added, then its collated at the end, we have to scatter it between each item.
        //data.end -= CGFloat(seperatorSpace)
        return data
    }

     func addShapeToCircle(data : pieAngle, percent: CGFloat) {
        let center = CGPoint(x: bounds.origin.x + bounds.size.width / 2, y: bounds.origin.y + bounds.size.height / 2)
        var  shapeLayer = CAShapeLayer()

        // radians = degrees * pi / 180
        // x*2 + y*2 = r*2
        //cos teta = x/r --> x = r * cos teta
        // sinn teta = y/ r -->  y = r * sin teta
//        let x = 100 * cos(data.start)
//        let y = 100 * sin(data.end)

        let radius = (bounds.origin.x + bounds.size.width / 2 - (sliceThickness)) / 2

        //This is the circle path drawn.
        let circularPath = UIBezierPath(arcCenter: .zero, radius: self.frame.width / 2, startAngle: data.start, endAngle: data.end, clockwise: true) //2*CGFloat.pi

        shapeLayer.path = circularPath.cgPath

        //Provide a bounding box for the shape layer to handle events
//Removing the below line works but will not handle touch events :(
        shapeLayer.bounds = circularPath.cgPath.boundingBox


        //Start the angle from anyplace you need { + - of Pi} // {0, 0.5 pi, 1 pi, 1.5pi}
       // shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2 , 0, 0, 1)
        // color of the stroke
        shapeLayer.strokeColor = data.color.cgColor

        //Width of stoke
        shapeLayer.lineWidth = sliceThickness

        //Starts from the center of the view
        shapeLayer.position = center

        //To provide a rounded cap on the stroke
        shapeLayer.lineCap = .round

        //Fills the entire circle with this color
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeEnd = 0
        basicAnim(shapeLayer: &shapeLayer, percentage: percent)
        layer.addSublayer(shapeLayer)
    }
    func basicAnim(shapeLayer: inout CAShapeLayer)  {
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        basicAnimation.toValue = 1
        basicAnimation.duration = 10

        //Forwards will hold the layer after completion
        basicAnimation.fillMode = .forwards
        basicAnimation.isRemovedOnCompletion = false
        shapeLayer.add(basicAnimation, forKey: "shapeLayerAniamtion")
    }

//    //Calucate percentage based on given values
//    public func calculateAngle(percentageVal:Double)-> CGFloat {
//        return CGFloat((percentageVal / 100) * 360)
//        let val = CGFloat (percentageVal / 100.0)
//        return val * 2 * CGFloat.pi
//    }

    private func calcualteStartAndEndAngle(items : [dataItem])-> [pieAngle] {
        var angle: pieAngle
        var angleToStart: CGFloat = 0.0

        //Add the total separator space to the circle so we can accurately measure the start point with space.
        var totalSeperatorSpace = Double(items.count) * separatorSpace

        var totalSum = items.reduce(CGFloat(totalSeperatorSpace)) { return $0 + $1.percentage }

        var angleList: [pieAngle] = []
        for item in items {
            //Find the end angle based on the percentage in the total circle
            let endAngle = (item.percentage / totalSum * 2 * .pi)  + angleToStart
            angle.0 = angleToStart
            angle.1 = endAngle
            angle.2 = item.color
            angleList.append(angle)
            angleToStart = endAngle
            //print(angle)
        }
        return angleList
    }

}


let container = UIView()
container.frame.size = CGSize(width: 360, height: 360)
container.backgroundColor = .white
PlaygroundPage.current.liveView = container
PlaygroundPage.current.needsIndefiniteExecution = true

let m = USBCircleChart(frame: CGRect(x: 0, y: 0, width: 215, height: 215))
m.center = CGPoint(x: container.bounds.size.width / 2, y: container.bounds.size.height / 2)

m.fillDataForChart(with: pieDataToDisplay)

container.addSubview(m)



swift core-animation calayer
1个回答
1
投票

您需要计算间距,然后从起点和终点角度相加并减去。.因此,用此一个更新您的calcualteStartAndEndAngle方法

 private func calcualteStartAndEndAngle(items : [dataItem])-> [pieAngle] {
        var angle: pieAngle
        var angleToStart: CGFloat = 0.0

        //Add the total separator space to the circle so we can accurately measure the start point with space.
        let totalSeperatorSpace = Double(items.count)



        let totalSum = items.reduce(CGFloat(totalSeperatorSpace)) { return $0 + $1.percentage }

        let spacing = CGFloat( totalSeperatorSpace + 1 ) / totalSum
        print("total Sum:\(spacing)")


        var angleList: [pieAngle] = []
        for item in items {
            //Find the end angle based on the percentage in the total circle
            let endAngle = (item.percentage / totalSum * 2 * .pi)  + angleToStart
            print("start:\(angleToStart) end:\(endAngle)")

            angle.0 = angleToStart + spacing
            angle.1 = endAngle - spacing
            angle.2 = item.color
            angleList.append(angle)
            angleToStart = endAngle + spacing
            //print(angle)
        }
        return angleList
    }

将产生此动画

enter image description here

如果需要线性动画,请更改动画方法

 private var timeOffset:CFTimeInterval = 0
func basicAnim(shapeLayer: inout CAShapeLayer, percentage:CGFloat)  {
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        basicAnimation.toValue = 1
        basicAnimation.duration = CFTimeInterval(percentage) 
        basicAnimation.beginTime = CACurrentMediaTime() + timeOffset
        print("timeOffset:\(timeOffset),")
        //Forwards will hold the layer after completion
        basicAnimation.fillMode = .forwards
        basicAnimation.isRemovedOnCompletion = false
        shapeLayer.add(basicAnimation, forKey: "shapeLayerAniamtion")

        timeOffset += CFTimeInterval(percentage)
    }

enter image description here

如果您想了解更多信息,请参见此框架RingPieChart

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.