Skip to content

Instantly share code, notes, and snippets.

@DreamingInBinary
Created August 14, 2018 20:10
Show Gist options
  • Save DreamingInBinary/a4a2d5183b8b9a093ef9f682028653c9 to your computer and use it in GitHub Desktop.
Save DreamingInBinary/a4a2d5183b8b9a093ef9f682028653c9 to your computer and use it in GitHub Desktop.

Revisions

  1. Jordan Morgan created this gist Aug 14, 2018.
    115 changes: 115 additions & 0 deletions UIView+BFRShimmering.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    //
    // UIView+BFRShimmer.h
    // BFRUtils
    //
    // Created by Jordan Morgan on 8/14/18.
    // Copyright © 2018 Buffer. All rights reserved.
    //

    #import <UIKit/UIKit.h>

    NS_ASSUME_NONNULL_BEGIN

    @interface UIView (BFRShimmer)

    @property (strong, nonatomic, readonly, nonnull) CAGradientLayer *gradient;
    @property (strong, nonatomic, readonly, nonnull) CABasicAnimation *shimmerAnimation;

    - (void)startShimmering;
    - (void)startShimmeringWithRepitions:(NSInteger)reps;
    - (void)endShimmering;

    @end

    NS_ASSUME_NONNULL_END


    //
    // UIView+BFRShimmer.m
    // BFRUtils
    //
    // Created by Jordan Morgan on 8/14/18.
    // Copyright © 2018 Buffer. All rights reserved.
    //

    #import "UIView+BFRShimmer.h"
    #import <objc/runtime.h>

    static NSString * const BFR_SHIMMER_KEY = @"bfr.Shimmering.animationKey";

    @implementation UIView (BFRShimmer)

    #pragma mark - Dynamic Properties

    static const void *GradientKey = &GradientKey;
    static const void *AnimationKey = &AnimationKey;

    - (CAGradientLayer *)gradient {
    CAGradientLayer *returnVal = objc_getAssociatedObject(self, GradientKey);

    if (!returnVal) {
    returnVal = [CAGradientLayer new];
    objc_setAssociatedObject(self, GradientKey, returnVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    return returnVal;
    }

    - (CABasicAnimation *)shimmerAnimation {
    CABasicAnimation *returnVal = objc_getAssociatedObject(self, AnimationKey);

    if (!returnVal) {
    returnVal = [CABasicAnimation animationWithKeyPath:@"position"];
    objc_setAssociatedObject(self, AnimationKey, returnVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    return returnVal;
    }

    #pragma mark - Public API

    - (void)startShimmeringWithRepitions:(NSInteger)reps {
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
    [self endShimmering];
    }];

    UIColor *lightColor = [[UIColor whiteColor] colorWithAlphaComponent:0.1];
    UIColor *darkColor = [[UIColor blackColor] colorWithAlphaComponent:1.0];

    CGFloat length = CGRectGetWidth(self.layer.bounds);
    CGFloat extraDistance = length + 1.0f * 0.4f;
    CGFloat fullShimmerLength = length * 3.0f + extraDistance;
    CGFloat travelDistance = length * 2.0f + extraDistance;
    CGFloat highlightOutsideLength = 0.0 / 2.0;
    CGFloat startPoint = (length + extraDistance) / fullShimmerLength;
    CGFloat endPoint = travelDistance / fullShimmerLength;

    self.gradient.bounds = CGRectMake(0.0, 0.0, fullShimmerLength, CGRectGetHeight(self.layer.bounds));
    self.gradient.position = CGPointMake(-travelDistance, 0.0);
    self.gradient.anchorPoint = CGPointZero;
    self.gradient.startPoint = CGPointMake(startPoint, 0.0);
    self.gradient.endPoint = CGPointMake(endPoint, 0.0);
    self.gradient.locations = @[@(highlightOutsideLength), @(0.5), @(1.0 - highlightOutsideLength)];
    self.gradient.colors = @[(id)darkColor.CGColor, (id)lightColor.CGColor, (id)darkColor.CGColor];

    self.shimmerAnimation.duration = 1.0;
    self.shimmerAnimation.repeatCount = reps > 0 ? reps : INFINITY;
    self.shimmerAnimation.toValue = [NSValue valueWithCGPoint:CGPointZero];

    [self.gradient addAnimation:self.shimmerAnimation forKey:BFR_SHIMMER_KEY];
    self.layer.mask = self.gradient;

    [CATransaction commit];
    }
    - (void)startShimmering {
    [self startShimmeringWithRepitions:0];
    }

    - (void)endShimmering {
    [self.layer.mask removeAnimationForKey:BFR_SHIMMER_KEY];
    self.layer.mask = nil;
    [self.layer setNeedsDisplay];
    }

    @end