Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation within headerView in Swift

I'm currently trying to add an animation to the user's xp bar.

If I had the animation in the collectionViewController, the animation is well loaded (once). However, if I had the animation within the headerView (because I would like to add the bar on the profile picture) the bar is launched more than one time:

enter image description here

This is my code (headerViewCell):

let shapeLayerXp = CAShapeLayer()

override func layoutSubviews() {
      super.layoutSubviews()
      showUserXp()
      self.animateXp(toValue: 1)
}

func showUserXp() {
      let center = profileImage.center
      let circularPath = UIBezierPath(arcCenter: center, radius: 40, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
      shapeLayerXp.path = circularPath.cgPath

      let color = UIColor(red: 122 / 255, green: 205 / 255, blue: 186 / 255, alpha: 1)

      shapeLayerXp.strokeColor = color.cgColor
      shapeLayerXp.lineWidth = 4
      shapeLayerXp.fillColor = UIColor.clear.cgColor
      shapeLayerXp.lineCap = kCALineCapRound

      shapeLayerXp.strokeEnd = 0

      self.contentView.layer.addSublayer(shapeLayerXp)


}

func animateXp(toValue: Int) {
      let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
      basicAnimation.toValue = toValue
      basicAnimation.duration = 2

      basicAnimation.fillMode = kCAFillModeForwards
      basicAnimation.isRemovedOnCompletion = false

      shapeLayerXp.add(basicAnimation, forKey: "urSoBasic")
}

The headerViewCell is launched like that:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

      let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

// ....

return cell

}
like image 579
KevinB Avatar asked Nov 16 '25 04:11

KevinB


1 Answers

Right assuming there is only one header cell ever in the controller unless the data is changed (e.g. a different user) then you can set a property on the view controller to indicate if the animation has been shown.

Something like this would do:

var animatedHeader = false // Starts false because we want an animation the first time.

Then when obtaining the header cell for the first time you can decide to fire the animation or not like this:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

      let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

      if !self.animatedHeader {
          cell.showUserXp()
          cell.animateXp()

          self.animatedHeader = true
      }

      return cell
}

The showUserXp() and animateXp() methods will need to be public of course.

Using this method the header cell will only animate the first time it is dequeued and therefore displayed.

If you did want to animate it again you just need to reset the animateHeader property and reload the collection view (or just the header).

If there is more than one header then you will need to keep track of each of them separately.

Edit: This does require (accidentally) the same cell to be used because of how the showXP and animateXP functions are defined. If I was doing this myself I would probably use something more like this approach:

func showUserXp(animated: Bool) {
    let center = profileImage.center
    let circularPath = UIBezierPath(arcCenter: center, radius: 40, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
    shapeLayerXp.path = circularPath.cgPath

    let color = UIColor(red: 122 / 255, green: 205 / 255, blue: 186 / 255, alpha: 1)

    shapeLayerXp.strokeColor = color.cgColor
    shapeLayerXp.lineWidth = 4
    shapeLayerXp.fillColor = UIColor.clear.cgColor
    shapeLayerXp.lineCap = kCALineCapRound

    shapeLayerXp.strokeEnd = 0

    self.contentView.layer.addSublayer(shapeLayerXp)

    if animated {
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        basicAnimation.toValue = toValue
        basicAnimation.duration = 2

        basicAnimation.fillMode = kCAFillModeForwards
        basicAnimation.isRemovedOnCompletion = false

        shapeLayerXp.add(basicAnimation, forKey: "urSoBasic")
    } else {
        shapeLayerXp.strokeEnd = 1
    }
}

Then you would use it like this:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

    let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

    if self.animatedHeader {
        cell.showUserXp(animated: false)
    } else {
        cell.showUserXp(animated: true)

        self.animatedHeader = true
    }

    return cell
}

So now you can show the header cell using an animation or not and whether you do use the animation is controlled by the animatedHeader property. This now no longer relies on a specific cell being dequeued.

like image 171
Upholder Of Truth Avatar answered Nov 18 '25 21:11

Upholder Of Truth