Как сделать UIBezierPath для заполнения из центра - PullRequest
0 голосов
/ 01 июля 2019

Как мне сделать UIBezierPath, который начинает рисовать из центра и расширяется влево и вправо?

Мое текущее решение не использует UIBezierPath, а скорее UIView внутри другого UIView.Детский вид центрируется внутри его родителя, и я настраиваю ширину дочернего элемента, когда мне нужно его расширить.Однако это решение далеко от совершенства, поскольку представление находится внутри UIStackView, а представление стека находится в ячейке UICollectionView, и иногда оно неправильно определяет ширину.

   override func layoutSubviews() {
    super.layoutSubviews()
    progressView.cornerRadius = cornerRadius
    clipsToBounds = true
    addSubview(progressView)
    NSLayoutConstraint.activate([
        progressView.topAnchor.constraint(equalTo: topAnchor),
        progressView.bottomAnchor.constraint(equalTo: bottomAnchor),
        progressView.centerXAnchor.constraint(equalTo: centerXAnchor)
    ])
    let width = frame.width
    let finalProgress = CGFloat(progress) * width
    let widthConstraint = progressView.widthAnchor.constraint(equalToConstant: finalProgress)
    widthConstraint.priority = UILayoutPriority(rawValue: 999)
    widthConstraint.isActive = true
}

прогресс - это свойство классатип double, и когда он установлен, я вызываю layoutIfNeeded.

Я также пытался вызвать setNeedsLayout и layoutIfNeeded, но это не решает проблему.SetNeedsDisplay и invalidateIntrinzicSize также не работают.

Теперь я хотел бы сделать это, используя путь Безье, но проблема в том, что он начинается с заданной точки и рисует слева направо.Вопрос в том, как мне сделать UIBezierPath по центру, и когда я увеличиваю ширину, он расширяется (остается в центре)

Вот изображение того, чего я хочу достичь.enter image description here

1 Ответ

2 голосов
/ 02 июля 2019

Вариантов довольно много. Но вот несколько:

  1. CABasicAnimation из strokeStart и strokeEnd из CAShapeLayer, а именно:

    • Создайте CAShapeLayer, чей path является последним UIBezierPath (полной шириной), но чьи strokeStart и strokeEnd оба равны 0,5 (то есть начните на полпути и закончите на полпути, т.е. пока ничего не видно).

      func configure() {
          shapeLayer.strokeColor = strokeColor.cgColor
          shapeLayer.lineCap = .round
          setProgress(0, animated: false)
      }
      
      func updatePath() {
          let path = UIBezierPath()
          path.move(to: CGPoint(x: bounds.minX + lineWidth, y: bounds.midY))
          path.addLine(to: CGPoint(x: bounds.maxX - lineWidth, y: bounds.midY))
          shapeLayer.path = path.cgPath
          shapeLayer.lineWidth = lineWidth
      }
      
    • Затем в CABasicAnimation анимируйте изменение strokeStart на 0 и strokeEnd на 1 для достижения анимации, растущей как слева, так и справа от середины. Например:

      func setProgress(_ progress: CGFloat, animated: Bool = true) {
          let percent = max(0, min(1, progress))
          let strokeStart = (1 - percent) / 2
          let strokeEnd = 1 - (1 - percent) / 2
      
          if animated {
              let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
              strokeStartAnimation.fromValue = shapeLayer.strokeStart
              strokeStartAnimation.toValue = strokeStart
      
              let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
              strokeEndAnimation.fromValue = shapeLayer.strokeEnd
              strokeEndAnimation.toValue = strokeEnd
      
              let animation = CAAnimationGroup()
              animation.animations = [strokeStartAnimation, strokeEndAnimation]
      
              shapeLayer.strokeStart = strokeStart
              shapeLayer.strokeEnd = strokeEnd
      
              layer.add(animation, forKey: nil)
          } else {
              shapeLayer.strokeStart = strokeStart
              shapeLayer.strokeEnd = strokeEnd
          }
      }
      
  2. Анимация path из CAShapeLayer

    • Есть метод, который создает UIBezierPath:

      func setProgress(_ progress: CGFloat, animated: Bool = true) {
          self.progress = progress
      
          let cgPath = path(for: progress).cgPath
      
          if animated {
              let animation = CABasicAnimation(keyPath: "path")
              animation.fromValue = shapeLayer.path
              animation.toValue = cgPath
      
              shapeLayer.path = cgPath
      
              layer.add(animation, forKey: nil)
          } else {
              shapeLayer.path = cgPath
          }
      }
      

      Где

      func path(for progress: CGFloat) -> UIBezierPath {
          let path = UIBezierPath()
          let percent = max(0, min(1, progress))
          let width = bounds.width - lineWidth
          path.move(to: CGPoint(x: bounds.midX - width * percent / 2 , y: bounds.midY))
          path.addLine(to: CGPoint(x: bounds.midX + width * percent / 2, y: bounds.midY))
          return path
      }
      
    • При создании вызова CAShapeLayer setProgress со значением 0 и без анимации; и

    • Если вы хотите анимировать его, позвоните setProgress со значением 1, но с анимацией.

  3. Отказаться UIBezierPath / CAShapeLayer подходит и использует UIView и UIView блочную анимацию:

    • Создайте короткий UIView с backgroundColor и слой которого имеет cornerRadius, что составляет половину высоты вида.

    • Определите ограничения, чтобы вид имел нулевую ширину. Например. вы можете определить centerXanchor, чтобы он был расположен там, где вам нужно, а widthAnchor равно нулю.

    • Анимируйте ограничения, установив для существующих widthAnchor 'constant любую ширину, которую вы хотите, и поместите layoutIfNeeded внутри блока анимации UIView.

...