Как я могу получить координаты от CAShapeLayer - PullRequest
1 голос
/ 03 марта 2020

Итак, я пытаюсь сделать индикатор прогресса. Итак, я сделал круговой путь, но я хочу, чтобы точка находилась в конце индикатора выполнения, но как мне получить положение точки в конце текущего прогресса?

private func simpleShape() {
  let width: CGFloat = 10
  createCircle()

  //make circle transparant in middle
  progressLayer.fillColor = UIColor.clear.cgColor
  progressLayer.strokeColor = UIColor.blue.cgColor
  progressLayer.lineCap = CAShapeLayerLineCap.round
  progressLayer.lineWidth = width
  progressLayer.strokeStart = 0
  progressLayer.strokeEnd = 0

  //unfilled
  backLayer.lineWidth = width
  backLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1).cgColor
  backLayer.strokeEnd = 1

  self.layer.addSublayer(gradientLayer)
}

private func createCircle() {

  //create circle
  let circle = UIView(frame: bounds)
  circle.layoutIfNeeded()
  let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
  let circleRadius: CGFloat = circle.bounds.width / 2 * 0.83

  let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.475 * Double.pi), endAngle: CGFloat(1.525 * Double.pi), clockwise: true)

  //add layers
  progressLayer.path = circlePath.cgPath
  backLayer.path = circlePath.cgPath
  circle.layer.addSublayer(backLayer)
  circle.layer.addSublayer(progressLayer)
  addSubview(circle)

  circle.layer.addSublayer(dotLayer)
}

let dotLayer = CAShapeLayer()

public func setProgress(_ progress: CGFloat) {
  progressLayer.strokeEnd = CGFloat(progress)

  if let progressEndpoint = progressLayer.path?.currentPoint {
    dotLayer.position = progressEndpoint
  }
}

This is what I'm getting

Это то, что я получаю

This is what I want

Это то, что я хочу

Ответы [ 2 ]

3 голосов
/ 03 марта 2020

Что вам нужно, это анимация вращения

let progressLayer = CAShapeLayer()
    let backLayer = CAShapeLayer()
    private func simpleShape() {
      let width: CGFloat = 15
      createCircle()

      //make circle transparant in middle
      progressLayer.fillColor = UIColor.clear.cgColor
      progressLayer.strokeColor = #colorLiteral(red: 0.888897419, green: 0.5411034822, blue: 0.04008810222, alpha: 1)
      progressLayer.lineCap = CAShapeLayerLineCap.round
      progressLayer.lineWidth = width
      progressLayer.strokeStart = 0
        progressLayer.strokeEnd = 0

      //unfilled
      backLayer.lineWidth = width
      backLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1)
      backLayer.strokeEnd = 1

     // self.layer.addSublayer(gradientLayer)
    }

    private func createCircle() {

      //create circle
      let circle = UIView(frame: bounds)
      let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
      let circleRadius: CGFloat = circle.bounds.width / 2 * 0.83
      let distance = circle.bounds.width / 2 * 0.17


      let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.475 * Double.pi), endAngle: CGFloat(1.525 * Double.pi), clockwise: true)

      //add layers
      progressLayer.path = circlePath.cgPath
      backLayer.path = circlePath.cgPath
      circle.layer.addSublayer(backLayer)
      circle.layer.addSublayer(progressLayer)
      addSubview(circle)


        let circleCenter = CGPoint(x:centerPoint.x - distance,y:centerPoint.y - circleRadius )

         let dotCircle = UIBezierPath()


        dotCircle.addArc(withCenter:circleCenter, radius: 3, startAngle: CGFloat(-90).deg2rad(), endAngle: CGFloat(270).deg2rad(), clockwise: true)

        dotLayer.path = dotCircle.cgPath
        dotLayer.position = CGPoint(x:centerPoint.x,y:centerPoint.y )
        dotLayer.strokeColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.6496753961)
        dotLayer.lineWidth = 10
        dotLayer.fillColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
        dotLayer.isHidden = true

      circle.layer.addSublayer(dotLayer)
    }

    let dotLayer = CAShapeLayer()

    public func setProgress(_ progress: CGFloat) {
        print(progress)
//      progressLayer.strokeEnd = progress
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.beginTime = CACurrentMediaTime() + 0.5;
        animation.fromValue = 0
        animation.toValue = progress
        animation.duration = 2
        animation.autoreverses = false
        animation.repeatCount = .nan
        animation.fillMode = .forwards
        animation.isRemovedOnCompletion = false
        progressLayer.add(animation, forKey: "line")


        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.dotLayer.isHidden = false
        let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
       // rotateAnimation.beginTime = CACurrentMediaTime() + 0.5;
        rotateAnimation.fromValue = (CGFloat( -90)).deg2rad()
        rotateAnimation.toValue = (360*progress - 98).deg2rad()
        rotateAnimation.duration = 2
        rotateAnimation.fillMode = .forwards
        rotateAnimation.isRemovedOnCompletion = false
        self.dotLayer.add(rotateAnimation, forKey: nil)
        }

    }

enter image description here

3 голосов
/ 03 марта 2020

Тебе придется рассчитать это самому. Итак, определите угол от начального и конечного углов для ваших дуг:

let angle = (endAngle - startAngle) * progress + startAngle

А затем используйте тригонометрию basi c, чтобы определить, где эта точка падает:

let point = CGPoint(x: centerPoint.x + radius * cos(angle),
                    y: centerPoint.y + radius * sin(angle))

dotLayer.position = point

По Кстати, я бы предложил отделить добавление подслоев (которое является частью процесса начальной настройки) от путей обновления (которые являются частью процесса компоновки представления, который может быть вызван снова, если кадр представления изменяется, ограничения применяются, et c). Таким образом, возможно:

@IBDesignable
class ProgressView: UIView {
    var progress: CGFloat = 0 { didSet { updateProgress() } }

    private var centerPoint: CGPoint = .zero
    private var radius: CGFloat = 0
    private let startAngle: CGFloat = -0.475 * .pi
    private let endAngle: CGFloat = 1.525 * .pi
    private let lineWidth: CGFloat = 10

    private lazy var progressLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.lineCap = .round
        shapeLayer.lineWidth = lineWidth
        shapeLayer.strokeStart = 0
        shapeLayer.strokeEnd = progress
        return shapeLayer
    }()

    private lazy var backLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = lineWidth
        shapeLayer.strokeColor = #colorLiteral(red: 0.1411764706, green: 0.1725490196, blue: 0.2431372549, alpha: 1).cgColor
        return shapeLayer
    }()

    private lazy var dotLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = UIBezierPath(arcCenter: .zero, radius: lineWidth / 2 * 1.75, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath
        shapeLayer.fillColor = UIColor.white.withAlphaComponent(0.5).cgColor
        return shapeLayer
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSublayers()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        addSublayers()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        updatePaths()
        updateProgress()
    }
}

private extension ProgressView {
    func addSublayers() {
        layer.addSublayer(backLayer)
        layer.addSublayer(progressLayer)
        layer.addSublayer(dotLayer)
    }

    func updatePaths() {
        centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
        radius = min(bounds.width, bounds.height) / 2 * 0.83

        let circlePath = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)

        progressLayer.path = circlePath.cgPath
        backLayer.path = circlePath.cgPath
    }

    func updateProgress() {
        progressLayer.strokeEnd = progress

        let angle = (endAngle - startAngle) * progress + startAngle
        let point = CGPoint(x: centerPoint.x + radius * cos(angle),
                            y: centerPoint.y + radius * sin(angle))

        dotLayer.position = point
    }
}
...