From 57d684c7986bd371ccdb6afe18bd688fd8dced10 Mon Sep 17 00:00:00 2001 From: Charles Powell Date: Sun, 17 May 2015 12:59:29 -0700 Subject: [PATCH] Rework gradient fade animations to use colors rather than locations, to prevent bug where overlapping gradient location stops still cause a ~1px fade. --- MarqueeLabel.m | 145 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/MarqueeLabel.m b/MarqueeLabel.m index 322627be..05aa7d4e 100755 --- a/MarqueeLabel.m +++ b/MarqueeLabel.m @@ -286,6 +286,7 @@ - (void)updateSublabelAndLocationsAndBeginScroll:(BOOL)beginScroll { // Calculate expected size CGSize expectedLabelSize = [self subLabelSize]; + // Invalidate intrinsic size [self invalidateIntrinsicContentSize]; @@ -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); @@ -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]; } } @@ -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]; @@ -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: @@ -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; }