Skip to content

Commit

Permalink
Rework gradient fade animations to use colors rather than locations, …
Browse files Browse the repository at this point in the history
…to prevent bug where overlapping gradient location stops still cause a ~1px fade.
  • Loading branch information
cbpowell committed May 17, 2015
1 parent 87395f2 commit 57d684c
Showing 1 changed file with 96 additions and 49 deletions.
145 changes: 96 additions & 49 deletions MarqueeLabel.m
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ - (void)updateSublabelAndLocationsAndBeginScroll:(BOOL)beginScroll {
// Calculate expected size
CGSize expectedLabelSize = [self subLabelSize];


// Invalidate intrinsic size
[self invalidateIntrinsicContentSize];

Expand Down Expand Up @@ -399,8 +400,8 @@ - (void)updateSublabelAndLocationsAndBeginScroll:(BOOL)beginScroll {
case MLLeftRight:
{
self.homeLabelFrame = CGRectIntegral(CGRectMake(self.leadingBuffer, 0.0f, expectedLabelSize.width, expectedLabelSize.height));
// self.awayLabelFrame = CGRectIntegral(CGRectOffset(self.homeLabelFrame, -expectedLabelSize.width + (self.bounds.size.width - self.fadeLength), 0.0));
self.awayLabelFrame = CGRectIntegral(CGRectOffset(self.homeLabelFrame, self.bounds.size.width - (expectedLabelSize.width + self.leadingBuffer + self.trailingBuffer), 0.0));

// Calculate animation duration
self.animationDuration = (self.rate != 0) ? ((NSTimeInterval)fabs(self.awayLabelFrame.origin.x - self.homeLabelFrame.origin.x) / self.rate) : (self.scrollDuration);

Expand Down Expand Up @@ -674,61 +675,77 @@ - (void)applyGradientMaskForFadeLength:(CGFloat)fadeLength animated:(BOOL)animat
// Create CAGradientLayer if needed
gradientMask = [CAGradientLayer layer];
}

// Set up colors
NSObject *transparent = (NSObject *)[[UIColor clearColor] CGColor];
NSObject *opaque = (NSObject *)[[UIColor blackColor] CGColor];

gradientMask.bounds = self.layer.bounds;
gradientMask.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
gradientMask.shouldRasterize = YES;
gradientMask.rasterizationScale = [UIScreen mainScreen].scale;
gradientMask.colors = self.gradientColors;
gradientMask.startPoint = CGPointMake(0.0f, CGRectGetMidY(self.frame));
gradientMask.endPoint = CGPointMake(1.0f, CGRectGetMidY(self.frame));
// Start with default (no fade) locations
gradientMask.startPoint = CGPointMake(0.0f, 0.5f);
gradientMask.endPoint = CGPointMake(1.0f, 0.5f);
// Start with "no fade" colors and locations
gradientMask.colors = @[opaque, opaque, opaque, opaque];
gradientMask.locations = @[@(0.0f), @(0.0f), @(1.0f), @(1.0f)];

// Set mask
self.layer.mask = gradientMask;

CGFloat leadingFadeLength = 0.0f;
CGFloat trailingFadeLength = fadeLength;
CGFloat leftFadeStop = fadeLength/self.bounds.size.width;
CGFloat rightFadeStop = fadeLength/self.bounds.size.width;

// No fade if labelized, or if no scrolling is needed
if (self.labelize || !self.labelShouldScroll) {
leadingFadeLength = 0.0f;
trailingFadeLength = 0.0f;
}
// Adjust stops based on fade length
NSArray *adjustedLocations = @[@(0.0), @(leftFadeStop), @(1.0 - rightFadeStop), @(1.0)];

CGFloat leftFadeLength, rightFadeLength;
// Determine colors for non-scrolling label (i.e. at home)
NSArray *adjustedColors;
BOOL trailingFadeNeeded = (!self.labelize || self.labelShouldScroll);
switch (self.marqueeType) {
case MLContinuousReverse:
case MLRightLeft:
leftFadeLength = trailingFadeLength;
rightFadeLength = leadingFadeLength;
adjustedColors = @[(trailingFadeNeeded ? transparent : opaque),
opaque,
opaque,
opaque];
break;

default:
// MLContinuous
// MLLeftRight
leftFadeLength = leadingFadeLength;
rightFadeLength = trailingFadeLength;
adjustedColors = @[opaque,
opaque,
opaque,
(trailingFadeNeeded ? transparent : opaque)];
break;
}

CGFloat leftFadePoint = leftFadeLength/self.bounds.size.width;
CGFloat rightFadePoint = rightFadeLength/self.bounds.size.width;

NSArray *adjustedLocations = @[@(0.0f), @(leftFadePoint), @(1.0f - rightFadePoint), @(1.0f)];
if (animated) {
// Create animation for gradient change
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"locations"];
animation.fromValue = gradientMask.locations;
animation.toValue = adjustedLocations;
animation.duration = 0.25;
// Create animation for location change
CABasicAnimation *locationAnimation = [CABasicAnimation animationWithKeyPath:@"locations"];
locationAnimation.fromValue = gradientMask.locations;
locationAnimation.toValue = adjustedLocations;
locationAnimation.duration = 0.25;

// Create animation for color change
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"colors"];
colorAnimation.fromValue = gradientMask.colors;
colorAnimation.toValue = adjustedColors;
colorAnimation.duration = 0.25;

CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.25;
group.animations = @[locationAnimation, colorAnimation];

[gradientMask addAnimation:animation forKey:animation.keyPath];
[gradientMask addAnimation:group forKey:colorAnimation.keyPath];
gradientMask.locations = adjustedLocations;
gradientMask.colors = adjustedColors;
} else {
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
gradientMask.locations = adjustedLocations;
gradientMask.colors = adjustedColors;
[CATransaction commit];
}
}
Expand All @@ -744,11 +761,12 @@ - (CAKeyframeAnimation *)keyFrameAnimationForGradientFadeLength:(CGFloat)fadeLen
// Setup
NSArray *values = nil;
NSArray *keyTimes = nil;
CGFloat fadeFraction = fadeLength/self.bounds.size.width;
NSTimeInterval totalDuration;
NSObject *transp = (NSObject *)[[UIColor clearColor] CGColor];
NSObject *opaque = (NSObject *)[[UIColor blackColor] CGColor];

// Create new animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"locations"];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"colors"];

// Get timing function
CAMediaTimingFunction *timingFunction = [self timingFunctionForAnimationOptions:self.animationCurve];
Expand All @@ -760,13 +778,15 @@ - (CAKeyframeAnimation *)keyFrameAnimationForGradientFadeLength:(CGFloat)fadeLen
// Calculate total animation duration
totalDuration = 2.0 * (delayAmount + interval);
keyTimes = @[
@(0.0), // Initial gradient
@(delayAmount/totalDuration), // Begin of fade in
@((delayAmount + 0.4)/totalDuration), // End of fade in, just as scroll away starts
@(0.95 * totalDuration/totalDuration), // Begin of fade out, just before scroll home completes
@(1.0), // End of fade out, as scroll home completes
@(1.0) // Buffer final value (used on continuous types)
];
@(0.0), // 1) Initial gradient
@(delayAmount/totalDuration), // 2) Begin of LE fade-in, just as scroll away starts
@((delayAmount + 0.4)/totalDuration), // 3) End of LE fade in [LE fully faded]
@((delayAmount + interval - 0.4)/totalDuration), // 4) Begin of TE fade out, just before scroll away finishes
@((delayAmount + interval)/totalDuration), // 5) End of TE fade out [TE fade removed]
@((delayAmount + interval + delayAmount)/totalDuration), // 6) Begin of TE fade back in, just as scroll home starts
@((delayAmount + interval + delayAmount + 0.4)/totalDuration), // 7) End of TE fade back in [TE fully faded]
@((totalDuration - 0.4)/totalDuration), // 8) Begin of LE fade out, just before scroll home finishes
@(1.0)]; // 9) End of LE fade out, just as scroll home finishes
break;

case MLContinuousReverse:
Expand Down Expand Up @@ -795,27 +815,54 @@ - (CAKeyframeAnimation *)keyFrameAnimationForGradientFadeLength:(CGFloat)fadeLen
// Define gradient values
switch (self.marqueeType) {
case MLContinuousReverse:
values = @[
@[transp, opaque, opaque, opaque], // Initial gradient
@[transp, opaque, opaque, opaque], // Begin of fade in
@[transp, opaque, opaque, transp], // End of fade in, just as scroll away starts
@[transp, opaque, opaque, transp], // Begin of fade out, just before scroll home completes
@[transp, opaque, opaque, opaque], // End of fade out, as scroll home completes
@[transp, opaque, opaque, opaque] // Final "home" value
];
break;

case MLRightLeft:
values = @[
@[@(0.0f), @(fadeFraction), @(1.0f), @(1.0f)], // Initial gradient
@[@(0.0f), @(fadeFraction), @(1.0f), @(1.0f)], // Begin of fade in
@[@(0.0f), @(fadeFraction), @(1.0f - fadeFraction), @(1.0f)], // End of fade in, just as scroll away starts
@[@(0.0f), @(fadeFraction), @(1.0f - fadeFraction), @(1.0f)], // Begin of fade out, just before scroll home completes
@[@(0.0f), @(fadeFraction), @(1.0f), @(1.0f)], // End of fade out, as scroll home completes
@[@(0.0f), @(fadeFraction), @(1.0f), @(1.0f)] // Final "home" value
@[transp, opaque, opaque, opaque], // 1)
@[transp, opaque, opaque, opaque], // 2)
@[transp, opaque, opaque, transp], // 3)
@[transp, opaque, opaque, transp], // 4)
@[opaque, opaque, opaque, transp], // 5)
@[opaque, opaque, opaque, transp], // 6)
@[transp, opaque, opaque, transp], // 7)
@[transp, opaque, opaque, transp], // 8)
@[transp, opaque, opaque, opaque] // 9)
];
break;

case MLContinuous:
values = @[
@[opaque, opaque, opaque, transp], // Initial gradient
@[opaque, opaque, opaque, transp], // Begin of fade in
@[transp, opaque, opaque, transp], // End of fade in, just as scroll away starts
@[transp, opaque, opaque, transp], // Begin of fade out, just before scroll home completes
@[opaque, opaque, opaque, transp], // End of fade out, as scroll home completes
@[opaque, opaque, opaque, transp] // Final "home" value
];
break;

case MLLeftRight:
default:
values = @[
@[@(0.0f), @(0.0f), @(1.0f - fadeFraction), @(1.0f)], // Initial gradient
@[@(0.0f), @(0.0f), @(1.0f - fadeFraction), @(1.0f)], // Begin of fade in
@[@(0.0f), @(fadeFraction), @(1.0f - fadeFraction), @(1.0f)], // End of fade in, just as scroll away starts
@[@(0.0f), @(fadeFraction), @(1.0f - fadeFraction), @(1.0f)], // Begin of fade out, just before scroll home completes
@[@(0.0f), @(0.0f), @(1.0f - fadeFraction), @(1.0f)], // End of fade out, as scroll home completes
@[@(0.0f), @(0.0f), @(1.0f - fadeFraction), @(1.0f)] // Final "home" value
];
@[opaque, opaque, opaque, transp], // 1)
@[opaque, opaque, opaque, transp], // 2)
@[transp, opaque, opaque, transp], // 3)
@[transp, opaque, opaque, transp], // 4)
@[transp, opaque, opaque, opaque], // 5)
@[transp, opaque, opaque, opaque], // 6)
@[transp, opaque, opaque, transp], // 7)
@[transp, opaque, opaque, transp], // 8)
@[opaque, opaque, opaque, transp] // 9)
];
break;
}

Expand Down

0 comments on commit 57d684c

Please sign in to comment.