UIButton subclass with animated 'shimmer' effect












6












$begingroup$


In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.



The effect I'm looking for is simpler:




  1. Button starts with single color (e.g. blue)

  2. a band of color (e.g. red) sweeps across the text label from left to right

  3. Repeat


Here's an example of my ShimmerButton class in action, and the code itself:



enter image description here



class ShimmerButton: UIButton {

private let wrapperLayer = CALayer()
private let gradientLayer = CAGradientLayer()

var gradientColors: [UIColor] = {
didSet {
gradientLayer.colors = gradientColors.map({ $0.cgColor })
}
}

override func layoutSubviews() {
super.layoutSubviews()

// only needs to be set once, but no harm (?) in setting multiple times
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
wrapperLayer.addSublayer(gradientLayer)
layer.insertSublayer(wrapperLayer, at: 0)
wrapperLayer.mask = titleLabel?.layer

// update sublayers based on new frame
wrapperLayer.frame = frame
gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)

// remove any existing animation, and re-create for new size
let animationKeyPath = "position.x"

gradientLayer.removeAnimation(forKey: animationKeyPath)

let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)

animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
animation.toValue = gradientLayer.bounds.width / 2
animation.duration = 3

animation.repeatCount = HUGE
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

gradientLayer.add(animation, forKey: animationKeyPath)
}
}


Example usage:



let shimmer = ShimmerButton(frame: .zero)
shimmer.backgroundColor = .white
shimmer.setTitle("Find new skills...", for: .normal)
shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
shimmer.sizeToFit()
shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]


Some questions:




  1. Code that only needs to be once off is happening in layoutSubviews so will be called multiple times. I did this so I didn't have to override init?(coder:) and init(frame:). Is this acceptable, or just lazy on my part?

  2. I'm animating the position of a CAGradientLayer to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?










share|improve this question











$endgroup$

















    6












    $begingroup$


    In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.



    The effect I'm looking for is simpler:




    1. Button starts with single color (e.g. blue)

    2. a band of color (e.g. red) sweeps across the text label from left to right

    3. Repeat


    Here's an example of my ShimmerButton class in action, and the code itself:



    enter image description here



    class ShimmerButton: UIButton {

    private let wrapperLayer = CALayer()
    private let gradientLayer = CAGradientLayer()

    var gradientColors: [UIColor] = {
    didSet {
    gradientLayer.colors = gradientColors.map({ $0.cgColor })
    }
    }

    override func layoutSubviews() {
    super.layoutSubviews()

    // only needs to be set once, but no harm (?) in setting multiple times
    gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
    gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
    wrapperLayer.addSublayer(gradientLayer)
    layer.insertSublayer(wrapperLayer, at: 0)
    wrapperLayer.mask = titleLabel?.layer

    // update sublayers based on new frame
    wrapperLayer.frame = frame
    gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)

    // remove any existing animation, and re-create for new size
    let animationKeyPath = "position.x"

    gradientLayer.removeAnimation(forKey: animationKeyPath)

    let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)

    animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
    animation.toValue = gradientLayer.bounds.width / 2
    animation.duration = 3

    animation.repeatCount = HUGE
    animation.fillMode = kCAFillModeForwards
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

    gradientLayer.add(animation, forKey: animationKeyPath)
    }
    }


    Example usage:



    let shimmer = ShimmerButton(frame: .zero)
    shimmer.backgroundColor = .white
    shimmer.setTitle("Find new skills...", for: .normal)
    shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
    shimmer.sizeToFit()
    shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]


    Some questions:




    1. Code that only needs to be once off is happening in layoutSubviews so will be called multiple times. I did this so I didn't have to override init?(coder:) and init(frame:). Is this acceptable, or just lazy on my part?

    2. I'm animating the position of a CAGradientLayer to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?










    share|improve this question











    $endgroup$















      6












      6








      6


      4



      $begingroup$


      In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.



      The effect I'm looking for is simpler:




      1. Button starts with single color (e.g. blue)

      2. a band of color (e.g. red) sweeps across the text label from left to right

      3. Repeat


      Here's an example of my ShimmerButton class in action, and the code itself:



      enter image description here



      class ShimmerButton: UIButton {

      private let wrapperLayer = CALayer()
      private let gradientLayer = CAGradientLayer()

      var gradientColors: [UIColor] = {
      didSet {
      gradientLayer.colors = gradientColors.map({ $0.cgColor })
      }
      }

      override func layoutSubviews() {
      super.layoutSubviews()

      // only needs to be set once, but no harm (?) in setting multiple times
      gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
      gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
      wrapperLayer.addSublayer(gradientLayer)
      layer.insertSublayer(wrapperLayer, at: 0)
      wrapperLayer.mask = titleLabel?.layer

      // update sublayers based on new frame
      wrapperLayer.frame = frame
      gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)

      // remove any existing animation, and re-create for new size
      let animationKeyPath = "position.x"

      gradientLayer.removeAnimation(forKey: animationKeyPath)

      let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)

      animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
      animation.toValue = gradientLayer.bounds.width / 2
      animation.duration = 3

      animation.repeatCount = HUGE
      animation.fillMode = kCAFillModeForwards
      animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

      gradientLayer.add(animation, forKey: animationKeyPath)
      }
      }


      Example usage:



      let shimmer = ShimmerButton(frame: .zero)
      shimmer.backgroundColor = .white
      shimmer.setTitle("Find new skills...", for: .normal)
      shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
      shimmer.sizeToFit()
      shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]


      Some questions:




      1. Code that only needs to be once off is happening in layoutSubviews so will be called multiple times. I did this so I didn't have to override init?(coder:) and init(frame:). Is this acceptable, or just lazy on my part?

      2. I'm animating the position of a CAGradientLayer to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?










      share|improve this question











      $endgroup$




      In earlier versions of iOS the lock screen had a 'slide to unlock' element which I'm referencing as a 'shimmer' effect.



      The effect I'm looking for is simpler:




      1. Button starts with single color (e.g. blue)

      2. a band of color (e.g. red) sweeps across the text label from left to right

      3. Repeat


      Here's an example of my ShimmerButton class in action, and the code itself:



      enter image description here



      class ShimmerButton: UIButton {

      private let wrapperLayer = CALayer()
      private let gradientLayer = CAGradientLayer()

      var gradientColors: [UIColor] = {
      didSet {
      gradientLayer.colors = gradientColors.map({ $0.cgColor })
      }
      }

      override func layoutSubviews() {
      super.layoutSubviews()

      // only needs to be set once, but no harm (?) in setting multiple times
      gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
      gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
      wrapperLayer.addSublayer(gradientLayer)
      layer.insertSublayer(wrapperLayer, at: 0)
      wrapperLayer.mask = titleLabel?.layer

      // update sublayers based on new frame
      wrapperLayer.frame = frame
      gradientLayer.frame.size = CGSize(width: frame.width * 4, height: frame.height)

      // remove any existing animation, and re-create for new size
      let animationKeyPath = "position.x"

      gradientLayer.removeAnimation(forKey: animationKeyPath)

      let animation: CABasicAnimation = CABasicAnimation(keyPath: animationKeyPath)

      animation.fromValue = bounds.width - gradientLayer.bounds.width / 2
      animation.toValue = gradientLayer.bounds.width / 2
      animation.duration = 3

      animation.repeatCount = HUGE
      animation.fillMode = kCAFillModeForwards
      animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

      gradientLayer.add(animation, forKey: animationKeyPath)
      }
      }


      Example usage:



      let shimmer = ShimmerButton(frame: .zero)
      shimmer.backgroundColor = .white
      shimmer.setTitle("Find new skills...", for: .normal)
      shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
      shimmer.sizeToFit()
      shimmer.gradientColors = [.blue, .blue, .red, .blue, .blue]


      Some questions:




      1. Code that only needs to be once off is happening in layoutSubviews so will be called multiple times. I did this so I didn't have to override init?(coder:) and init(frame:). Is this acceptable, or just lazy on my part?

      2. I'm animating the position of a CAGradientLayer to get the visual effect, but found that I needed to use another layer as a wrapper otherwise the text itself would move. Am I overlooking a solution that involves fewer layers?







      swift animation swift3






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Mar 26 '17 at 16:30









      Jamal

      30.4k11121227




      30.4k11121227










      asked Mar 20 '17 at 20:07









      MathewSMathewS

      568311




      568311






















          2 Answers
          2






          active

          oldest

          votes


















          6












          $begingroup$

          Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.



          I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations property on CAGradientLayer which is also animatable.



          Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.



          Then, with without the wrapper layer I realized that I could override layerClass so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews.



          The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations).



          Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint and gradientHighlight properties that are used to set the gradients colors array.



          I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.



          It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).



          protocol ShimmerEffect {
          var animationDuration: TimeInterval { set get }
          var animationDelay: TimeInterval {set get }

          var gradientTint: UIColor { set get }
          var gradientHighlight: UIColor { set get }

          //// Expects value between 0.0—1.0 that represents
          //// the ratio of the gradient highlight to the full
          //// width of the gradient.
          var gradientHighlightRatio: Double { set get }

          //// The layer that the gradient will be applied to
          var gradientLayer: CAGradientLayer { get }
          }


          Default implementation:



          extension ShimmerEffect {

          /// Configures, and adds the animation to the gradientLayer
          func addShimmerAnimation() {

          // `gradientHighlightRatio` represents how wide the highlight
          // should be compared to the entire width of the gradient and
          // is used to calculate the positions of the 3 gradient colors.
          // If the highlight is 20% width of the gradient, then the
          // 'start locations' would be [-0.2, -0.1, 0.0] and the
          // 'end locations' would be [1.0, 1.1, 1.2]
          let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
          let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
          let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]

          // If the gradient highlight ratio is wide, then it can
          // 'bleed' over into the visible space of the view, which
          // looks particularly bad if there is a pause between the
          // animation repeating.
          // Shifting the start and end points of the gradient by the
          // size of the highlight prevents this.
          gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
          gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
          gradientLayer.locations = startLocations
          gradientLayer.colors = gradientColors

          let animationKeyPath = "locations"

          let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
          shimmerAnimation.fromValue = startLocations
          shimmerAnimation.toValue = endLocations
          shimmerAnimation.duration = animationDuration
          shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

          let animationGroup = CAAnimationGroup()
          animationGroup.duration = animationDuration + animationDelay
          animationGroup.repeatCount = .infinity
          animationGroup.animations = [shimmerAnimation]

          // removes animation with same key (if exists) then adds
          // the new animation
          gradientLayer.removeAnimation(forKey: animationKeyPath)
          gradientLayer.add(animationGroup, forKey: animationKeyPath)
          }
          }


          In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation() with any property change.



          I also considered just supplying default values and requiring addShimmerAnimation() to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable.



          class ShimmerButton: UIButton, ShimmerEffect {

          override static var layerClass: AnyClass {
          return CAGradientLayer.self
          }

          var gradientLayer: CAGradientLayer {
          return layer as! CAGradientLayer
          }

          var animationDuration: TimeInterval = 3 {
          didSet { addShimmerAnimation() }
          }
          var animationDelay: TimeInterval = 1.5 {
          didSet { addShimmerAnimation() }
          }

          var gradientHighlightRatio: Double = 0.3 {
          didSet { addShimmerAnimation() }
          }

          var gradientTint: UIColor = .black {
          didSet { addShimmerAnimation() }
          }

          var gradientHighlight: UIColor = .white {
          didSet { addShimmerAnimation() }
          }

          override init(frame: CGRect) {
          super.init(frame: frame)
          gradientLayer.mask = titleLabel?.layer
          addShimmerAnimation()
          }

          required init?(coder aDecoder: NSCoder) {
          super.init(coder: aDecoder)
          gradientLayer.mask = titleLabel?.layer
          addShimmerAnimation()
          }
          }


          Example usage:



          let shimmer = ShimmerButton()
          shimmer.setTitle("Find new skills", for: .normal)
          shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
          shimmer.gradientTint = darkBlue
          shimmer.gradientHighlight = lightBlue
          shimmer.sizeToFit()


          What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.



          What frustrates me is that UIView, UILabel and UIButton only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.



          Example of ShimmerButton and ShimmerView (a UIView subclass) being used together:



          enter image description here






          share|improve this answer











          $endgroup$





















            0












            $begingroup$

            Shimmer animation can be added like below in iOS



            class ViewController: UIViewController {
            @IBOutlet var label: UILabel!
            let gradientLayer = CAGradientLayer()
            override func viewDidLoad() {
            super.viewDidLoad()
            self.gradientLayer.frame = self.label.bounds
            self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
            self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
            self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
            let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
            let endLocations : [NSNumber] = [1.0,1.5, 2.0]

            self.gradientLayer.locations = startLocations
            let animation = CABasicAnimation(keyPath: "locations")
            animation.fromValue = startLocations
            animation.toValue = endLocations
            animation.duration = 0.8
            animation.repeatCount = .infinity

            self.gradientLayer.add(animation, forKey: animation.keyPath)
            self.label.layer.addSublayer(self.gradientLayer)

            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.gradientLayer.removeAllAnimations()
            }
            }


            }





            share|improve this answer








            New contributor




            Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
            Check out our Code of Conduct.






            $endgroup$













              Your Answer





              StackExchange.ifUsing("editor", function () {
              return StackExchange.using("mathjaxEditing", function () {
              StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
              StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
              });
              });
              }, "mathjax-editing");

              StackExchange.ifUsing("editor", function () {
              StackExchange.using("externalEditor", function () {
              StackExchange.using("snippets", function () {
              StackExchange.snippets.init();
              });
              });
              }, "code-snippets");

              StackExchange.ready(function() {
              var channelOptions = {
              tags: "".split(" "),
              id: "196"
              };
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function() {
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled) {
              StackExchange.using("snippets", function() {
              createEditor();
              });
              }
              else {
              createEditor();
              }
              });

              function createEditor() {
              StackExchange.prepareEditor({
              heartbeatType: 'answer',
              autoActivateHeartbeat: false,
              convertImagesToLinks: false,
              noModals: true,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: null,
              bindNavPrevention: true,
              postfix: "",
              imageUploader: {
              brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
              contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
              allowUrls: true
              },
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              });


              }
              });














              draft saved

              draft discarded


















              StackExchange.ready(
              function () {
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f158336%2fuibutton-subclass-with-animated-shimmer-effect%23new-answer', 'question_page');
              }
              );

              Post as a guest















              Required, but never shown

























              2 Answers
              2






              active

              oldest

              votes








              2 Answers
              2






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              6












              $begingroup$

              Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.



              I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations property on CAGradientLayer which is also animatable.



              Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.



              Then, with without the wrapper layer I realized that I could override layerClass so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews.



              The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations).



              Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint and gradientHighlight properties that are used to set the gradients colors array.



              I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.



              It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).



              protocol ShimmerEffect {
              var animationDuration: TimeInterval { set get }
              var animationDelay: TimeInterval {set get }

              var gradientTint: UIColor { set get }
              var gradientHighlight: UIColor { set get }

              //// Expects value between 0.0—1.0 that represents
              //// the ratio of the gradient highlight to the full
              //// width of the gradient.
              var gradientHighlightRatio: Double { set get }

              //// The layer that the gradient will be applied to
              var gradientLayer: CAGradientLayer { get }
              }


              Default implementation:



              extension ShimmerEffect {

              /// Configures, and adds the animation to the gradientLayer
              func addShimmerAnimation() {

              // `gradientHighlightRatio` represents how wide the highlight
              // should be compared to the entire width of the gradient and
              // is used to calculate the positions of the 3 gradient colors.
              // If the highlight is 20% width of the gradient, then the
              // 'start locations' would be [-0.2, -0.1, 0.0] and the
              // 'end locations' would be [1.0, 1.1, 1.2]
              let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
              let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
              let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]

              // If the gradient highlight ratio is wide, then it can
              // 'bleed' over into the visible space of the view, which
              // looks particularly bad if there is a pause between the
              // animation repeating.
              // Shifting the start and end points of the gradient by the
              // size of the highlight prevents this.
              gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
              gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
              gradientLayer.locations = startLocations
              gradientLayer.colors = gradientColors

              let animationKeyPath = "locations"

              let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
              shimmerAnimation.fromValue = startLocations
              shimmerAnimation.toValue = endLocations
              shimmerAnimation.duration = animationDuration
              shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

              let animationGroup = CAAnimationGroup()
              animationGroup.duration = animationDuration + animationDelay
              animationGroup.repeatCount = .infinity
              animationGroup.animations = [shimmerAnimation]

              // removes animation with same key (if exists) then adds
              // the new animation
              gradientLayer.removeAnimation(forKey: animationKeyPath)
              gradientLayer.add(animationGroup, forKey: animationKeyPath)
              }
              }


              In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation() with any property change.



              I also considered just supplying default values and requiring addShimmerAnimation() to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable.



              class ShimmerButton: UIButton, ShimmerEffect {

              override static var layerClass: AnyClass {
              return CAGradientLayer.self
              }

              var gradientLayer: CAGradientLayer {
              return layer as! CAGradientLayer
              }

              var animationDuration: TimeInterval = 3 {
              didSet { addShimmerAnimation() }
              }
              var animationDelay: TimeInterval = 1.5 {
              didSet { addShimmerAnimation() }
              }

              var gradientHighlightRatio: Double = 0.3 {
              didSet { addShimmerAnimation() }
              }

              var gradientTint: UIColor = .black {
              didSet { addShimmerAnimation() }
              }

              var gradientHighlight: UIColor = .white {
              didSet { addShimmerAnimation() }
              }

              override init(frame: CGRect) {
              super.init(frame: frame)
              gradientLayer.mask = titleLabel?.layer
              addShimmerAnimation()
              }

              required init?(coder aDecoder: NSCoder) {
              super.init(coder: aDecoder)
              gradientLayer.mask = titleLabel?.layer
              addShimmerAnimation()
              }
              }


              Example usage:



              let shimmer = ShimmerButton()
              shimmer.setTitle("Find new skills", for: .normal)
              shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
              shimmer.gradientTint = darkBlue
              shimmer.gradientHighlight = lightBlue
              shimmer.sizeToFit()


              What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.



              What frustrates me is that UIView, UILabel and UIButton only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.



              Example of ShimmerButton and ShimmerView (a UIView subclass) being used together:



              enter image description here






              share|improve this answer











              $endgroup$


















                6












                $begingroup$

                Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.



                I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations property on CAGradientLayer which is also animatable.



                Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.



                Then, with without the wrapper layer I realized that I could override layerClass so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews.



                The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations).



                Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint and gradientHighlight properties that are used to set the gradients colors array.



                I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.



                It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).



                protocol ShimmerEffect {
                var animationDuration: TimeInterval { set get }
                var animationDelay: TimeInterval {set get }

                var gradientTint: UIColor { set get }
                var gradientHighlight: UIColor { set get }

                //// Expects value between 0.0—1.0 that represents
                //// the ratio of the gradient highlight to the full
                //// width of the gradient.
                var gradientHighlightRatio: Double { set get }

                //// The layer that the gradient will be applied to
                var gradientLayer: CAGradientLayer { get }
                }


                Default implementation:



                extension ShimmerEffect {

                /// Configures, and adds the animation to the gradientLayer
                func addShimmerAnimation() {

                // `gradientHighlightRatio` represents how wide the highlight
                // should be compared to the entire width of the gradient and
                // is used to calculate the positions of the 3 gradient colors.
                // If the highlight is 20% width of the gradient, then the
                // 'start locations' would be [-0.2, -0.1, 0.0] and the
                // 'end locations' would be [1.0, 1.1, 1.2]
                let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
                let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
                let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]

                // If the gradient highlight ratio is wide, then it can
                // 'bleed' over into the visible space of the view, which
                // looks particularly bad if there is a pause between the
                // animation repeating.
                // Shifting the start and end points of the gradient by the
                // size of the highlight prevents this.
                gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
                gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
                gradientLayer.locations = startLocations
                gradientLayer.colors = gradientColors

                let animationKeyPath = "locations"

                let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
                shimmerAnimation.fromValue = startLocations
                shimmerAnimation.toValue = endLocations
                shimmerAnimation.duration = animationDuration
                shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

                let animationGroup = CAAnimationGroup()
                animationGroup.duration = animationDuration + animationDelay
                animationGroup.repeatCount = .infinity
                animationGroup.animations = [shimmerAnimation]

                // removes animation with same key (if exists) then adds
                // the new animation
                gradientLayer.removeAnimation(forKey: animationKeyPath)
                gradientLayer.add(animationGroup, forKey: animationKeyPath)
                }
                }


                In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation() with any property change.



                I also considered just supplying default values and requiring addShimmerAnimation() to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable.



                class ShimmerButton: UIButton, ShimmerEffect {

                override static var layerClass: AnyClass {
                return CAGradientLayer.self
                }

                var gradientLayer: CAGradientLayer {
                return layer as! CAGradientLayer
                }

                var animationDuration: TimeInterval = 3 {
                didSet { addShimmerAnimation() }
                }
                var animationDelay: TimeInterval = 1.5 {
                didSet { addShimmerAnimation() }
                }

                var gradientHighlightRatio: Double = 0.3 {
                didSet { addShimmerAnimation() }
                }

                var gradientTint: UIColor = .black {
                didSet { addShimmerAnimation() }
                }

                var gradientHighlight: UIColor = .white {
                didSet { addShimmerAnimation() }
                }

                override init(frame: CGRect) {
                super.init(frame: frame)
                gradientLayer.mask = titleLabel?.layer
                addShimmerAnimation()
                }

                required init?(coder aDecoder: NSCoder) {
                super.init(coder: aDecoder)
                gradientLayer.mask = titleLabel?.layer
                addShimmerAnimation()
                }
                }


                Example usage:



                let shimmer = ShimmerButton()
                shimmer.setTitle("Find new skills", for: .normal)
                shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
                shimmer.gradientTint = darkBlue
                shimmer.gradientHighlight = lightBlue
                shimmer.sizeToFit()


                What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.



                What frustrates me is that UIView, UILabel and UIButton only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.



                Example of ShimmerButton and ShimmerView (a UIView subclass) being used together:



                enter image description here






                share|improve this answer











                $endgroup$
















                  6












                  6








                  6





                  $begingroup$

                  Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.



                  I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations property on CAGradientLayer which is also animatable.



                  Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.



                  Then, with without the wrapper layer I realized that I could override layerClass so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews.



                  The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations).



                  Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint and gradientHighlight properties that are used to set the gradients colors array.



                  I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.



                  It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).



                  protocol ShimmerEffect {
                  var animationDuration: TimeInterval { set get }
                  var animationDelay: TimeInterval {set get }

                  var gradientTint: UIColor { set get }
                  var gradientHighlight: UIColor { set get }

                  //// Expects value between 0.0—1.0 that represents
                  //// the ratio of the gradient highlight to the full
                  //// width of the gradient.
                  var gradientHighlightRatio: Double { set get }

                  //// The layer that the gradient will be applied to
                  var gradientLayer: CAGradientLayer { get }
                  }


                  Default implementation:



                  extension ShimmerEffect {

                  /// Configures, and adds the animation to the gradientLayer
                  func addShimmerAnimation() {

                  // `gradientHighlightRatio` represents how wide the highlight
                  // should be compared to the entire width of the gradient and
                  // is used to calculate the positions of the 3 gradient colors.
                  // If the highlight is 20% width of the gradient, then the
                  // 'start locations' would be [-0.2, -0.1, 0.0] and the
                  // 'end locations' would be [1.0, 1.1, 1.2]
                  let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
                  let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
                  let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]

                  // If the gradient highlight ratio is wide, then it can
                  // 'bleed' over into the visible space of the view, which
                  // looks particularly bad if there is a pause between the
                  // animation repeating.
                  // Shifting the start and end points of the gradient by the
                  // size of the highlight prevents this.
                  gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
                  gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
                  gradientLayer.locations = startLocations
                  gradientLayer.colors = gradientColors

                  let animationKeyPath = "locations"

                  let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
                  shimmerAnimation.fromValue = startLocations
                  shimmerAnimation.toValue = endLocations
                  shimmerAnimation.duration = animationDuration
                  shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

                  let animationGroup = CAAnimationGroup()
                  animationGroup.duration = animationDuration + animationDelay
                  animationGroup.repeatCount = .infinity
                  animationGroup.animations = [shimmerAnimation]

                  // removes animation with same key (if exists) then adds
                  // the new animation
                  gradientLayer.removeAnimation(forKey: animationKeyPath)
                  gradientLayer.add(animationGroup, forKey: animationKeyPath)
                  }
                  }


                  In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation() with any property change.



                  I also considered just supplying default values and requiring addShimmerAnimation() to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable.



                  class ShimmerButton: UIButton, ShimmerEffect {

                  override static var layerClass: AnyClass {
                  return CAGradientLayer.self
                  }

                  var gradientLayer: CAGradientLayer {
                  return layer as! CAGradientLayer
                  }

                  var animationDuration: TimeInterval = 3 {
                  didSet { addShimmerAnimation() }
                  }
                  var animationDelay: TimeInterval = 1.5 {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientHighlightRatio: Double = 0.3 {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientTint: UIColor = .black {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientHighlight: UIColor = .white {
                  didSet { addShimmerAnimation() }
                  }

                  override init(frame: CGRect) {
                  super.init(frame: frame)
                  gradientLayer.mask = titleLabel?.layer
                  addShimmerAnimation()
                  }

                  required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
                  gradientLayer.mask = titleLabel?.layer
                  addShimmerAnimation()
                  }
                  }


                  Example usage:



                  let shimmer = ShimmerButton()
                  shimmer.setTitle("Find new skills", for: .normal)
                  shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
                  shimmer.gradientTint = darkBlue
                  shimmer.gradientHighlight = lightBlue
                  shimmer.sizeToFit()


                  What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.



                  What frustrates me is that UIView, UILabel and UIButton only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.



                  Example of ShimmerButton and ShimmerView (a UIView subclass) being used together:



                  enter image description here






                  share|improve this answer











                  $endgroup$



                  Okay, so sometimes posting a question is the best way to figure out an answer yourself 🙃.



                  I was looking for an alternative to having the gradient colors evenly spaced (my hack was to repeat the colors e.g. "blue blue red blue blue") and found the locations property on CAGradientLayer which is also animatable.



                  Animating this property feels like a much better approach because with the position not changing I can remove the wrapper layer.



                  Then, with without the wrapper layer I realized that I could override layerClass so the buttons backing layer is a gradient layer, which will also resize as needed when the view frame changes so that I don't even need to override layoutSubviews.



                  The only thing that feels a little strange is the forced requirement for for gradient colors to have three colors (otherwise I'd need to figure some formula to derive values for locations).



                  Edit: I've updated answer so instead of directly setting the colors, I've exposed gradientTint and gradientHighlight properties that are used to set the gradients colors array.



                  I've created a protocol that captures the properties used to define the shimmer effect, and also provide a default implementation of the animation.



                  It wasn't a requirement from my original question, but moving this code out of a specific subclass makes this snippet of code reusable (and maintainable) across other subclasses (e.g. UIView, UILabel).



                  protocol ShimmerEffect {
                  var animationDuration: TimeInterval { set get }
                  var animationDelay: TimeInterval {set get }

                  var gradientTint: UIColor { set get }
                  var gradientHighlight: UIColor { set get }

                  //// Expects value between 0.0—1.0 that represents
                  //// the ratio of the gradient highlight to the full
                  //// width of the gradient.
                  var gradientHighlightRatio: Double { set get }

                  //// The layer that the gradient will be applied to
                  var gradientLayer: CAGradientLayer { get }
                  }


                  Default implementation:



                  extension ShimmerEffect {

                  /// Configures, and adds the animation to the gradientLayer
                  func addShimmerAnimation() {

                  // `gradientHighlightRatio` represents how wide the highlight
                  // should be compared to the entire width of the gradient and
                  // is used to calculate the positions of the 3 gradient colors.
                  // If the highlight is 20% width of the gradient, then the
                  // 'start locations' would be [-0.2, -0.1, 0.0] and the
                  // 'end locations' would be [1.0, 1.1, 1.2]
                  let startLocations = [NSNumber(value: -gradientHighlightRatio), NSNumber(value: -gradientHighlightRatio/2), 0.0]
                  let endLocations = [1, NSNumber(value: 1+(gradientHighlightRatio/2)), NSNumber(value: 1+gradientHighlightRatio)]
                  let gradientColors = [gradientTint.cgColor, gradientHighlight.cgColor, gradientTint.cgColor]

                  // If the gradient highlight ratio is wide, then it can
                  // 'bleed' over into the visible space of the view, which
                  // looks particularly bad if there is a pause between the
                  // animation repeating.
                  // Shifting the start and end points of the gradient by the
                  // size of the highlight prevents this.
                  gradientLayer.startPoint = CGPoint(x: -gradientHighlightRatio, y: 0.5)
                  gradientLayer.endPoint = CGPoint(x: 1+gradientHighlightRatio, y: 0.5)
                  gradientLayer.locations = startLocations
                  gradientLayer.colors = gradientColors

                  let animationKeyPath = "locations"

                  let shimmerAnimation = CABasicAnimation(keyPath: animationKeyPath)
                  shimmerAnimation.fromValue = startLocations
                  shimmerAnimation.toValue = endLocations
                  shimmerAnimation.duration = animationDuration
                  shimmerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

                  let animationGroup = CAAnimationGroup()
                  animationGroup.duration = animationDuration + animationDelay
                  animationGroup.repeatCount = .infinity
                  animationGroup.animations = [shimmerAnimation]

                  // removes animation with same key (if exists) then adds
                  // the new animation
                  gradientLayer.removeAnimation(forKey: animationKeyPath)
                  gradientLayer.add(animationGroup, forKey: animationKeyPath)
                  }
                  }


                  In the UIButton subclass I've added property observers to each of the properties that calls addShimmerAnimation() with any property change.



                  I also considered just supplying default values and requiring addShimmerAnimation() to be called manually once properties were configured. Another route was not having any public properties exposed and instead passing everything in through an initializer, but that would remove the possibility of these classes being used in a storyboard (which is an option I like to leave open) and having properties exposed through tagging the properties with IBInspectable.



                  class ShimmerButton: UIButton, ShimmerEffect {

                  override static var layerClass: AnyClass {
                  return CAGradientLayer.self
                  }

                  var gradientLayer: CAGradientLayer {
                  return layer as! CAGradientLayer
                  }

                  var animationDuration: TimeInterval = 3 {
                  didSet { addShimmerAnimation() }
                  }
                  var animationDelay: TimeInterval = 1.5 {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientHighlightRatio: Double = 0.3 {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientTint: UIColor = .black {
                  didSet { addShimmerAnimation() }
                  }

                  var gradientHighlight: UIColor = .white {
                  didSet { addShimmerAnimation() }
                  }

                  override init(frame: CGRect) {
                  super.init(frame: frame)
                  gradientLayer.mask = titleLabel?.layer
                  addShimmerAnimation()
                  }

                  required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
                  gradientLayer.mask = titleLabel?.layer
                  addShimmerAnimation()
                  }
                  }


                  Example usage:



                  let shimmer = ShimmerButton()
                  shimmer.setTitle("Find new skills", for: .normal)
                  shimmer.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightHeavy)
                  shimmer.gradientTint = darkBlue
                  shimmer.gradientHighlight = lightBlue
                  shimmer.sizeToFit()


                  What I like about this approach is that the complexity is moved out of the subclass making it super easy to duplicate over other views.



                  What frustrates me is that UIView, UILabel and UIButton only have minor differences. I wish there was a way for the computed properties to be extracted into a common place.



                  Example of ShimmerButton and ShimmerView (a UIView subclass) being used together:



                  enter image description here







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Mar 21 '17 at 17:56

























                  answered Mar 20 '17 at 20:51









                  MathewSMathewS

                  568311




                  568311

























                      0












                      $begingroup$

                      Shimmer animation can be added like below in iOS



                      class ViewController: UIViewController {
                      @IBOutlet var label: UILabel!
                      let gradientLayer = CAGradientLayer()
                      override func viewDidLoad() {
                      super.viewDidLoad()
                      self.gradientLayer.frame = self.label.bounds
                      self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
                      self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
                      self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
                      let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
                      let endLocations : [NSNumber] = [1.0,1.5, 2.0]

                      self.gradientLayer.locations = startLocations
                      let animation = CABasicAnimation(keyPath: "locations")
                      animation.fromValue = startLocations
                      animation.toValue = endLocations
                      animation.duration = 0.8
                      animation.repeatCount = .infinity

                      self.gradientLayer.add(animation, forKey: animation.keyPath)
                      self.label.layer.addSublayer(self.gradientLayer)

                      DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                      self.gradientLayer.removeAllAnimations()
                      }
                      }


                      }





                      share|improve this answer








                      New contributor




                      Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                      Check out our Code of Conduct.






                      $endgroup$


















                        0












                        $begingroup$

                        Shimmer animation can be added like below in iOS



                        class ViewController: UIViewController {
                        @IBOutlet var label: UILabel!
                        let gradientLayer = CAGradientLayer()
                        override func viewDidLoad() {
                        super.viewDidLoad()
                        self.gradientLayer.frame = self.label.bounds
                        self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
                        self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
                        self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
                        let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
                        let endLocations : [NSNumber] = [1.0,1.5, 2.0]

                        self.gradientLayer.locations = startLocations
                        let animation = CABasicAnimation(keyPath: "locations")
                        animation.fromValue = startLocations
                        animation.toValue = endLocations
                        animation.duration = 0.8
                        animation.repeatCount = .infinity

                        self.gradientLayer.add(animation, forKey: animation.keyPath)
                        self.label.layer.addSublayer(self.gradientLayer)

                        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                        self.gradientLayer.removeAllAnimations()
                        }
                        }


                        }





                        share|improve this answer








                        New contributor




                        Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                        Check out our Code of Conduct.






                        $endgroup$
















                          0












                          0








                          0





                          $begingroup$

                          Shimmer animation can be added like below in iOS



                          class ViewController: UIViewController {
                          @IBOutlet var label: UILabel!
                          let gradientLayer = CAGradientLayer()
                          override func viewDidLoad() {
                          super.viewDidLoad()
                          self.gradientLayer.frame = self.label.bounds
                          self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
                          self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
                          self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
                          let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
                          let endLocations : [NSNumber] = [1.0,1.5, 2.0]

                          self.gradientLayer.locations = startLocations
                          let animation = CABasicAnimation(keyPath: "locations")
                          animation.fromValue = startLocations
                          animation.toValue = endLocations
                          animation.duration = 0.8
                          animation.repeatCount = .infinity

                          self.gradientLayer.add(animation, forKey: animation.keyPath)
                          self.label.layer.addSublayer(self.gradientLayer)

                          DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                          self.gradientLayer.removeAllAnimations()
                          }
                          }


                          }





                          share|improve this answer








                          New contributor




                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.






                          $endgroup$



                          Shimmer animation can be added like below in iOS



                          class ViewController: UIViewController {
                          @IBOutlet var label: UILabel!
                          let gradientLayer = CAGradientLayer()
                          override func viewDidLoad() {
                          super.viewDidLoad()
                          self.gradientLayer.frame = self.label.bounds
                          self.gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
                          self.gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
                          self.gradientLayer.colors = [UIColor.red.cgColor, UIColor.white.cgColor, UIColor.darkGray.cgColor]
                          let startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
                          let endLocations : [NSNumber] = [1.0,1.5, 2.0]

                          self.gradientLayer.locations = startLocations
                          let animation = CABasicAnimation(keyPath: "locations")
                          animation.fromValue = startLocations
                          animation.toValue = endLocations
                          animation.duration = 0.8
                          animation.repeatCount = .infinity

                          self.gradientLayer.add(animation, forKey: animation.keyPath)
                          self.label.layer.addSublayer(self.gradientLayer)

                          DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                          self.gradientLayer.removeAllAnimations()
                          }
                          }


                          }






                          share|improve this answer








                          New contributor




                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.









                          share|improve this answer



                          share|improve this answer






                          New contributor




                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.









                          answered 1 hour ago









                          Alok SInhaAlok SInha

                          1011




                          1011




                          New contributor




                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.





                          New contributor





                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.






                          Alok SInha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                          Check out our Code of Conduct.






























                              draft saved

                              draft discarded




















































                              Thanks for contributing an answer to Code Review Stack Exchange!


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid



                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.


                              Use MathJax to format equations. MathJax reference.


                              To learn more, see our tips on writing great answers.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function () {
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f158336%2fuibutton-subclass-with-animated-shimmer-effect%23new-answer', 'question_page');
                              }
                              );

                              Post as a guest















                              Required, but never shown





















































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown

































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown







                              Popular posts from this blog

                              Список кардиналов, возведённых папой римским Каликстом III

                              Deduzione

                              Mysql.sock missing - “Can't connect to local MySQL server through socket”