Initial commit
This commit is contained in:
Executable
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, FBShimmerDirection) {
|
||||
FBShimmerDirectionRight, // Shimmer animation goes from left to right
|
||||
FBShimmerDirectionLeft, // Shimmer animation goes from right to left
|
||||
FBShimmerDirectionUp, // Shimmer animation goes from below to above
|
||||
FBShimmerDirectionDown, // Shimmer animation goes from above to below
|
||||
};
|
||||
|
||||
@protocol FBShimmering <NSObject>
|
||||
|
||||
//! @abstract Set this to YES to start shimming and NO to stop. Defaults to NO.
|
||||
@property (nonatomic, assign, readwrite, getter = isShimmering) BOOL shimmering;
|
||||
|
||||
//! @abstract The time interval between shimmerings in seconds. Defaults to 0.4.
|
||||
@property (assign, nonatomic, readwrite) CFTimeInterval shimmeringPauseDuration;
|
||||
|
||||
//! @abstract The opacity of the content while it is shimmering. Defaults to 1.0.
|
||||
@property (assign, nonatomic, readwrite) CGFloat shimmeringAnimationOpacity;
|
||||
|
||||
//! @abstract The opacity of the content before it is shimmering. Defaults to 0.5.
|
||||
@property (assign, nonatomic, readwrite) CGFloat shimmeringOpacity;
|
||||
|
||||
//! @abstract The speed of shimmering, in points per second. Defaults to 230.
|
||||
@property (assign, nonatomic, readwrite) CGFloat shimmeringSpeed;
|
||||
|
||||
//! @abstract The highlight length of shimmering. Range of [0,1], defaults to 0.33.
|
||||
@property (assign, nonatomic, readwrite) CGFloat shimmeringHighlightLength;
|
||||
|
||||
//! @abstract @deprecated Same as "shimmeringHighlightLength", just for downward compatibility
|
||||
@property (assign, nonatomic, readwrite, getter = shimmeringHighlightLength, setter = setShimmeringHighlightLength:) CGFloat shimmeringHighlightWidth;
|
||||
|
||||
//! @abstract The direction of shimmering animation. Defaults to FBShimmerDirectionRight.
|
||||
@property (assign, nonatomic, readwrite) FBShimmerDirection shimmeringDirection;
|
||||
|
||||
//! @abstract The duration of the fade used when shimmer begins. Defaults to 0.1.
|
||||
@property (assign, nonatomic, readwrite) CFTimeInterval shimmeringBeginFadeDuration;
|
||||
|
||||
//! @abstract The duration of the fade used when shimmer ends. Defaults to 0.3.
|
||||
@property (assign, nonatomic, readwrite) CFTimeInterval shimmeringEndFadeDuration;
|
||||
|
||||
/**
|
||||
@abstract The absolute CoreAnimation media time when the shimmer will fade in.
|
||||
@discussion Only valid after setting {@ref shimmering} to NO.
|
||||
*/
|
||||
@property (assign, nonatomic, readonly) CFTimeInterval shimmeringFadeTime;
|
||||
|
||||
@end
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/CALayer.h>
|
||||
|
||||
#import "FBShimmering.h"
|
||||
|
||||
/**
|
||||
@abstract Lightweight, generic shimmering layer.
|
||||
*/
|
||||
@interface FBShimmeringLayer : CALayer <FBShimmering>
|
||||
|
||||
//! @abstract The content layer to be shimmered.
|
||||
@property (strong, nonatomic) CALayer *contentLayer;
|
||||
|
||||
@end
|
||||
Executable
+468
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "FBShimmeringLayer.h"
|
||||
|
||||
#import <QuartzCore/CAAnimation.h>
|
||||
#import <QuartzCore/CAGradientLayer.h>
|
||||
#import <QuartzCore/CATransaction.h>
|
||||
|
||||
#import <UIKit/UIGeometry.h>
|
||||
#import <UIKit/UIColor.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag.
|
||||
#endif
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
UIKIT_EXTERN float UIAnimationDragCoefficient(void); // UIKit private drag coeffient, use judiciously
|
||||
#endif
|
||||
|
||||
static CGFloat FBShimmeringLayerDragCoefficient(void)
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
return UIAnimationDragCoefficient();
|
||||
#else
|
||||
return 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void FBShimmeringLayerAnimationApplyDragCoefficient(CAAnimation *animation)
|
||||
{
|
||||
CGFloat k = FBShimmeringLayerDragCoefficient();
|
||||
|
||||
if (k != 0 && k != 1) {
|
||||
animation.speed = 1 / k;
|
||||
}
|
||||
}
|
||||
|
||||
// animations keys
|
||||
static NSString *const kFBShimmerSlideAnimationKey = @"slide";
|
||||
static NSString *const kFBFadeAnimationKey = @"fade";
|
||||
static NSString *const kFBEndFadeAnimationKey = @"fade-end";
|
||||
|
||||
static CABasicAnimation *fade_animation(id delegate, CALayer *layer, CGFloat opacity, CFTimeInterval duration)
|
||||
{
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
animation.delegate = delegate;
|
||||
animation.fromValue = @([(layer.presentationLayer ?: layer) opacity]);
|
||||
animation.toValue = @(opacity);
|
||||
animation.fillMode = kCAFillModeBoth;
|
||||
animation.removedOnCompletion = NO;
|
||||
animation.duration = duration;
|
||||
FBShimmeringLayerAnimationApplyDragCoefficient(animation);
|
||||
return animation;
|
||||
}
|
||||
|
||||
static CABasicAnimation *shimmer_begin_fade_animation(id delegate, CALayer *layer, CGFloat opacity, CGFloat duration)
|
||||
{
|
||||
return fade_animation(delegate, layer, opacity, duration);
|
||||
}
|
||||
|
||||
static CABasicAnimation *shimmer_end_fade_animation(id delegate, CALayer *layer, CGFloat opacity, CGFloat duration)
|
||||
{
|
||||
CABasicAnimation *animation = fade_animation(delegate, layer, opacity, duration);
|
||||
[animation setValue:@YES forKey:kFBEndFadeAnimationKey];
|
||||
return animation;
|
||||
}
|
||||
|
||||
static CABasicAnimation *shimmer_slide_animation(id delegate, CFTimeInterval duration, FBShimmerDirection direction)
|
||||
{
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
|
||||
animation.delegate = delegate;
|
||||
animation.toValue = [NSValue valueWithCGPoint:CGPointZero];
|
||||
animation.duration = duration;
|
||||
animation.repeatCount = HUGE_VALF;
|
||||
FBShimmeringLayerAnimationApplyDragCoefficient(animation);
|
||||
if (direction == FBShimmerDirectionLeft ||
|
||||
direction == FBShimmerDirectionUp) {
|
||||
animation.speed = -fabsf(animation.speed);
|
||||
}
|
||||
return animation;
|
||||
}
|
||||
|
||||
// take a shimmer slide animation and turns into repeating
|
||||
static CAAnimation *shimmer_slide_repeat(CAAnimation *a, CFTimeInterval duration, FBShimmerDirection direction)
|
||||
{
|
||||
CAAnimation *anim = [a copy];
|
||||
anim.repeatCount = HUGE_VALF;
|
||||
anim.duration = duration;
|
||||
anim.speed = (direction == FBShimmerDirectionRight || direction == FBShimmerDirectionDown) ? fabsf(anim.speed) : -fabsf(anim.speed);
|
||||
return anim;
|
||||
}
|
||||
|
||||
// take a shimmer slide animation and turns into finish
|
||||
static CAAnimation *shimmer_slide_finish(CAAnimation *a)
|
||||
{
|
||||
CAAnimation *anim = [a copy];
|
||||
anim.repeatCount = 0;
|
||||
return anim;
|
||||
}
|
||||
|
||||
@interface FBShimmeringMaskLayer : CAGradientLayer
|
||||
@property (readonly, nonatomic) CALayer *fadeLayer;
|
||||
@end
|
||||
|
||||
@implementation FBShimmeringMaskLayer
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (nil != self) {
|
||||
_fadeLayer = [[CALayer alloc] init];
|
||||
_fadeLayer.backgroundColor = [UIColor whiteColor].CGColor;
|
||||
[self addSublayer:_fadeLayer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSublayers
|
||||
{
|
||||
[super layoutSublayers];
|
||||
CGRect r = self.bounds;
|
||||
_fadeLayer.bounds = r;
|
||||
_fadeLayer.position = CGPointMake(CGRectGetMidX(r), CGRectGetMidY(r));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FBShimmeringLayer ()
|
||||
@property (strong, nonatomic) FBShimmeringMaskLayer *maskLayer;
|
||||
@end
|
||||
|
||||
@implementation FBShimmeringLayer
|
||||
{
|
||||
CALayer *_contentLayer;
|
||||
FBShimmeringMaskLayer *_maskLayer;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
@synthesize shimmering = _shimmering;
|
||||
@synthesize shimmeringPauseDuration = _shimmeringPauseDuration;
|
||||
@synthesize shimmeringAnimationOpacity = _shimmeringAnimationOpacity;
|
||||
@synthesize shimmeringOpacity = _shimmeringOpacity;
|
||||
@synthesize shimmeringSpeed = _shimmeringSpeed;
|
||||
@synthesize shimmeringHighlightLength = _shimmeringHighlightLength;
|
||||
@synthesize shimmeringDirection = _shimmeringDirection;
|
||||
@synthesize shimmeringFadeTime = _shimmeringFadeTime;
|
||||
@synthesize shimmeringBeginFadeDuration = _shimmeringBeginFadeDuration;
|
||||
@synthesize shimmeringEndFadeDuration = _shimmeringEndFadeDuration;
|
||||
@dynamic shimmeringHighlightWidth;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (nil != self) {
|
||||
// default configuration
|
||||
_shimmeringPauseDuration = 0.4;
|
||||
_shimmeringSpeed = 230.0;
|
||||
_shimmeringHighlightLength = 1.0;
|
||||
_shimmeringAnimationOpacity = 0.5;
|
||||
_shimmeringOpacity = 1.0;
|
||||
_shimmeringDirection = FBShimmerDirectionRight;
|
||||
_shimmeringBeginFadeDuration = 0.1;
|
||||
_shimmeringEndFadeDuration = 0.3;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setContentLayer:(CALayer *)contentLayer
|
||||
{
|
||||
// reset mask
|
||||
self.maskLayer = nil;
|
||||
|
||||
// note content layer and add for display
|
||||
_contentLayer = contentLayer;
|
||||
self.sublayers = contentLayer ? @[contentLayer] : nil;
|
||||
|
||||
// update shimmering animation
|
||||
[self _updateShimmering];
|
||||
}
|
||||
|
||||
- (void)setShimmering:(BOOL)shimmering
|
||||
{
|
||||
if (shimmering != _shimmering) {
|
||||
_shimmering = shimmering;
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringSpeed:(CGFloat)speed
|
||||
{
|
||||
if (speed != _shimmeringSpeed) {
|
||||
_shimmeringSpeed = speed;
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringHighlightLength:(CGFloat)length
|
||||
{
|
||||
if (length != _shimmeringHighlightLength) {
|
||||
_shimmeringHighlightLength = length;
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringDirection:(FBShimmerDirection)direction
|
||||
{
|
||||
if (direction != _shimmeringDirection) {
|
||||
_shimmeringDirection = direction;
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringPauseDuration:(CFTimeInterval)duration
|
||||
{
|
||||
if (duration != _shimmeringPauseDuration) {
|
||||
_shimmeringPauseDuration = duration;
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringAnimationOpacity:(CGFloat)shimmeringAnimationOpacity
|
||||
{
|
||||
if (shimmeringAnimationOpacity != _shimmeringAnimationOpacity) {
|
||||
_shimmeringAnimationOpacity = shimmeringAnimationOpacity;
|
||||
[self _updateMaskColors];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShimmeringOpacity:(CGFloat)shimmeringOpacity
|
||||
{
|
||||
if (shimmeringOpacity != _shimmeringOpacity) {
|
||||
_shimmeringOpacity = shimmeringOpacity;
|
||||
[self _updateMaskColors];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSublayers
|
||||
{
|
||||
[super layoutSublayers];
|
||||
CGRect r = self.bounds;
|
||||
_contentLayer.anchorPoint = CGPointMake(0.5, 0.5);
|
||||
_contentLayer.bounds = r;
|
||||
_contentLayer.position = CGPointMake(CGRectGetMidX(r), CGRectGetMidY(r));
|
||||
|
||||
if (nil != _maskLayer) {
|
||||
[self _updateMaskLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
if (!CGRectEqualToRect(self.bounds, bounds)) {
|
||||
[super setBounds:bounds];
|
||||
|
||||
[self _updateShimmering];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (void)_clearMask
|
||||
{
|
||||
if (nil == _maskLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL disableActions = [CATransaction disableActions];
|
||||
[CATransaction setDisableActions:YES];
|
||||
|
||||
self.maskLayer = nil;
|
||||
_contentLayer.mask = nil;
|
||||
|
||||
[CATransaction setDisableActions:disableActions];
|
||||
}
|
||||
|
||||
- (void)_createMaskIfNeeded
|
||||
{
|
||||
if (_shimmering && !_maskLayer) {
|
||||
_maskLayer = [FBShimmeringMaskLayer layer];
|
||||
_maskLayer.delegate = self;
|
||||
_contentLayer.mask = _maskLayer;
|
||||
[self _updateMaskColors];
|
||||
[self _updateMaskLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateMaskColors
|
||||
{
|
||||
if (nil == _maskLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We create a gradient to be used as a mask.
|
||||
// In a mask, the colors do not matter, it's the alpha that decides the degree of masking.
|
||||
UIColor *maskedColor = [UIColor colorWithWhite:1.0 alpha:_shimmeringOpacity];
|
||||
UIColor *unmaskedColor = [UIColor colorWithWhite:1.0 alpha:_shimmeringAnimationOpacity];
|
||||
|
||||
// Create a gradient from masked to unmasked to masked.
|
||||
_maskLayer.colors = @[(__bridge id)maskedColor.CGColor, (__bridge id)unmaskedColor.CGColor, (__bridge id)maskedColor.CGColor];
|
||||
}
|
||||
|
||||
- (void)_updateMaskLayout
|
||||
{
|
||||
// Everything outside the mask layer is hidden, so we need to create a mask long enough for the shimmered layer to be always covered by the mask.
|
||||
CGFloat length = 0.0f;
|
||||
if (_shimmeringDirection == FBShimmerDirectionDown ||
|
||||
_shimmeringDirection == FBShimmerDirectionUp) {
|
||||
length = CGRectGetHeight(_contentLayer.bounds);
|
||||
} else {
|
||||
length = CGRectGetWidth(_contentLayer.bounds);
|
||||
}
|
||||
if (0 == length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// extra distance for the gradient to travel during the pause.
|
||||
CGFloat extraDistance = length + _shimmeringSpeed * _shimmeringPauseDuration;
|
||||
|
||||
// compute how far the shimmering goes
|
||||
CGFloat fullShimmerLength = length * 3.0f + extraDistance;
|
||||
CGFloat travelDistance = length * 2.0f + extraDistance;
|
||||
|
||||
// position the gradient for the desired width
|
||||
CGFloat highlightOutsideLength = (1.0 - _shimmeringHighlightLength) / 2.0;
|
||||
_maskLayer.locations = @[@(highlightOutsideLength),
|
||||
@(0.5),
|
||||
@(1.0 - highlightOutsideLength)];
|
||||
|
||||
CGFloat startPoint = (length + extraDistance) / fullShimmerLength;
|
||||
CGFloat endPoint = travelDistance / fullShimmerLength;
|
||||
|
||||
// position for the start of the animation
|
||||
_maskLayer.anchorPoint = CGPointZero;
|
||||
if (_shimmeringDirection == FBShimmerDirectionDown ||
|
||||
_shimmeringDirection == FBShimmerDirectionUp) {
|
||||
_maskLayer.startPoint = CGPointMake(0.0, startPoint);
|
||||
_maskLayer.endPoint = CGPointMake(0.0, endPoint);
|
||||
_maskLayer.position = CGPointMake(0.0, -travelDistance);
|
||||
_maskLayer.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(_contentLayer.bounds), fullShimmerLength);
|
||||
} else {
|
||||
_maskLayer.startPoint = CGPointMake(startPoint, 0.0);
|
||||
_maskLayer.endPoint = CGPointMake(endPoint, 0.0);
|
||||
_maskLayer.position = CGPointMake(-travelDistance, 0.0);
|
||||
_maskLayer.bounds = CGRectMake(0.0, 0.0, fullShimmerLength, CGRectGetHeight(_contentLayer.bounds));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateShimmering
|
||||
{
|
||||
// create mask if needed
|
||||
[self _createMaskIfNeeded];
|
||||
|
||||
// if not shimmering and no mask, noop
|
||||
if (!_shimmering && !_maskLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure layed out
|
||||
[self layoutIfNeeded];
|
||||
|
||||
BOOL disableActions = [CATransaction disableActions];
|
||||
if (!_shimmering) {
|
||||
if (disableActions) {
|
||||
// simply remove mask
|
||||
[self _clearMask];
|
||||
} else {
|
||||
// end slide
|
||||
CFTimeInterval slideEndTime = 0;
|
||||
|
||||
CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];
|
||||
if (slideAnimation != nil) {
|
||||
// determing total time sliding
|
||||
CFTimeInterval now = CACurrentMediaTime();
|
||||
CFTimeInterval slideTotalDuration = now - slideAnimation.beginTime;
|
||||
|
||||
// determine time offset into current slide
|
||||
CFTimeInterval slideTimeOffset = fmod(slideTotalDuration, slideAnimation.duration);
|
||||
|
||||
// transition to non-repeating slide
|
||||
CAAnimation *finishAnimation = shimmer_slide_finish(slideAnimation);
|
||||
|
||||
// adjust begin time to now - offset
|
||||
finishAnimation.beginTime = now - slideTimeOffset;
|
||||
|
||||
// note slide end time and begin
|
||||
slideEndTime = finishAnimation.beginTime + slideAnimation.duration;
|
||||
[_maskLayer addAnimation:finishAnimation forKey:kFBShimmerSlideAnimationKey];
|
||||
}
|
||||
|
||||
// fade in text at slideEndTime
|
||||
CABasicAnimation *fadeInAnimation = shimmer_end_fade_animation(self, _maskLayer.fadeLayer, 1.0, _shimmeringEndFadeDuration);
|
||||
fadeInAnimation.beginTime = slideEndTime;
|
||||
[_maskLayer.fadeLayer addAnimation:fadeInAnimation forKey:kFBFadeAnimationKey];
|
||||
|
||||
// expose end time for synchronization
|
||||
_shimmeringFadeTime = slideEndTime;
|
||||
}
|
||||
} else {
|
||||
// fade out text, optionally animated
|
||||
CABasicAnimation *fadeOutAnimation = nil;
|
||||
if (_shimmeringBeginFadeDuration > 0.0 && !disableActions) {
|
||||
fadeOutAnimation = shimmer_begin_fade_animation(self, _maskLayer.fadeLayer, 0.0, _shimmeringBeginFadeDuration);
|
||||
[_maskLayer.fadeLayer addAnimation:fadeOutAnimation forKey:kFBFadeAnimationKey];
|
||||
} else {
|
||||
BOOL innerDisableActions = [CATransaction disableActions];
|
||||
[CATransaction setDisableActions:YES];
|
||||
|
||||
_maskLayer.fadeLayer.opacity = 0.0;
|
||||
[_maskLayer.fadeLayer removeAllAnimations];
|
||||
|
||||
[CATransaction setDisableActions:innerDisableActions];
|
||||
}
|
||||
|
||||
// begin slide animation
|
||||
CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];
|
||||
|
||||
// compute shimmer duration
|
||||
CGFloat length = 0.0f;
|
||||
if (_shimmeringDirection == FBShimmerDirectionDown ||
|
||||
_shimmeringDirection == FBShimmerDirectionUp) {
|
||||
length = CGRectGetHeight(_contentLayer.bounds);
|
||||
} else {
|
||||
length = CGRectGetWidth(_contentLayer.bounds);
|
||||
}
|
||||
CFTimeInterval animationDuration = (length / _shimmeringSpeed) + _shimmeringPauseDuration;
|
||||
|
||||
if (slideAnimation != nil) {
|
||||
// ensure existing slide animation repeats
|
||||
[_maskLayer addAnimation:shimmer_slide_repeat(slideAnimation, animationDuration, _shimmeringDirection) forKey:kFBShimmerSlideAnimationKey];
|
||||
} else {
|
||||
// add slide animation
|
||||
slideAnimation = shimmer_slide_animation(self, animationDuration, _shimmeringDirection);
|
||||
slideAnimation.fillMode = kCAFillModeForwards;
|
||||
slideAnimation.removedOnCompletion = NO;
|
||||
slideAnimation.beginTime = CACurrentMediaTime() + fadeOutAnimation.duration;
|
||||
[_maskLayer addAnimation:slideAnimation forKey:kFBShimmerSlideAnimationKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - CALayerDelegate
|
||||
|
||||
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
|
||||
{
|
||||
// no associated actions
|
||||
return (id)kCFNull;
|
||||
}
|
||||
|
||||
#pragma mark - CAAnimationDelegate
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
|
||||
{
|
||||
if (flag && [[anim valueForKey:kFBEndFadeAnimationKey] boolValue]) {
|
||||
[self _clearMask];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIView.h>
|
||||
|
||||
#import "FBShimmering.h"
|
||||
|
||||
/**
|
||||
@abstract Lightweight, generic shimmering view.
|
||||
*/
|
||||
@interface FBShimmeringView : UIView <FBShimmering>
|
||||
|
||||
//! @abstract The content view to be shimmered.
|
||||
@property (strong, nonatomic) UIView *contentView;
|
||||
|
||||
@end
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "FBShimmeringView.h"
|
||||
|
||||
#import "FBShimmeringLayer.h"
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag.
|
||||
#endif
|
||||
|
||||
@implementation FBShimmeringView
|
||||
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [FBShimmeringLayer class];
|
||||
}
|
||||
|
||||
#define __layer ((FBShimmeringLayer *)self.layer)
|
||||
|
||||
#define LAYER_ACCESSOR(accessor, ctype) \
|
||||
- (ctype)accessor { \
|
||||
return [__layer accessor]; \
|
||||
}
|
||||
|
||||
#define LAYER_MUTATOR(mutator, ctype) \
|
||||
- (void)mutator (ctype)value { \
|
||||
[__layer mutator value]; \
|
||||
}
|
||||
|
||||
#define LAYER_RW_PROPERTY(accessor, mutator, ctype) \
|
||||
LAYER_ACCESSOR (accessor, ctype) \
|
||||
LAYER_MUTATOR (mutator, ctype)
|
||||
|
||||
LAYER_RW_PROPERTY(isShimmering, setShimmering:, BOOL)
|
||||
LAYER_RW_PROPERTY(shimmeringPauseDuration, setShimmeringPauseDuration:, CFTimeInterval)
|
||||
LAYER_RW_PROPERTY(shimmeringAnimationOpacity, setShimmeringAnimationOpacity:, CGFloat)
|
||||
LAYER_RW_PROPERTY(shimmeringOpacity, setShimmeringOpacity:, CGFloat)
|
||||
LAYER_RW_PROPERTY(shimmeringSpeed, setShimmeringSpeed:, CGFloat)
|
||||
LAYER_RW_PROPERTY(shimmeringHighlightLength, setShimmeringHighlightLength:, CGFloat)
|
||||
LAYER_RW_PROPERTY(shimmeringDirection, setShimmeringDirection:, FBShimmerDirection)
|
||||
LAYER_ACCESSOR(shimmeringFadeTime, CFTimeInterval)
|
||||
LAYER_RW_PROPERTY(shimmeringBeginFadeDuration, setShimmeringBeginFadeDuration:, CFTimeInterval)
|
||||
LAYER_RW_PROPERTY(shimmeringEndFadeDuration, setShimmeringEndFadeDuration:, CFTimeInterval)
|
||||
|
||||
- (void)setContentView:(UIView *)contentView
|
||||
{
|
||||
if (contentView != _contentView) {
|
||||
_contentView = contentView;
|
||||
[self addSubview:contentView];
|
||||
__layer.contentLayer = contentView.layer;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user