| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- //
- // CBAutoScrollLabel.m
- // CBAutoScrollLabel
- //
- // Created by Brian Stormont on 10/21/09.
- // Updated by Christopher Bess on 2/5/12
- //
- // Copyright 2009 Stormy Productions.
- //
- // Permission is granted to use this code free of charge for any project.
- //
- #import "CBAutoScrollLabel.h"
- #import <QuartzCore/QuartzCore.h>
- #define kLabelCount 2
- #define kDefaultFadeLength 7.f
- // pixel buffer space between scrolling label
- #define kDefaultLabelBufferSpace 20
- #define kDefaultPixelsPerSecond 30
- #define kDefaultPauseTime 1.5f
- // shortcut method for NSArray iterations
- static void each_object(NSArray *objects, void (^block)(id object)) {
- for (id obj in objects) {
- block(obj);
- }
- }
- // shortcut to change each label attribute value
- #define EACH_LABEL(ATTR, VALUE) each_object(self.labels, ^(UILabel *label) { label.ATTR = VALUE; });
- @interface CBAutoScrollLabel ()
- @property (nonatomic, strong) NSArray<UILabel *> *labels;
- @property (nonatomic, strong, readonly) UILabel *mainLabel;
- @property (nonatomic, strong) UIScrollView *scrollView;
- @end
- @implementation CBAutoScrollLabel
- - (id)initWithCoder:(NSCoder *)aDecoder {
- if ((self = [super initWithCoder:aDecoder])) {
- [self commonInit];
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame {
- if ((self = [super initWithFrame:frame])) {
- [self commonInit];
- }
- return self;
- }
- - (void)commonInit {
- // create the labels
- NSMutableSet<UILabel *> *labelSet = [[NSMutableSet alloc] initWithCapacity:kLabelCount];
- for (int index = 0; index < kLabelCount; ++index) {
- UILabel *label = [[UILabel alloc] init];
- label.backgroundColor = [UIColor clearColor];
- label.autoresizingMask = self.autoresizingMask;
- // store labels
- [self.scrollView addSubview:label];
- [labelSet addObject:label];
- }
- self.labels = [labelSet.allObjects copy];
- // default values
- _scrollDirection = CBAutoScrollDirectionLeft;
- _scrollSpeed = kDefaultPixelsPerSecond;
- self.pauseInterval = kDefaultPauseTime;
- self.labelSpacing = kDefaultLabelBufferSpace;
- self.textAlignment = NSTextAlignmentLeft;
- self.animationOptions = UIViewAnimationOptionCurveLinear;
- self.scrollView.showsVerticalScrollIndicator = NO;
- self.scrollView.showsHorizontalScrollIndicator = NO;
- self.scrollView.scrollEnabled = NO;
- self.userInteractionEnabled = NO;
- self.backgroundColor = [UIColor clearColor];
- self.clipsToBounds = YES;
- self.fadeLength = kDefaultFadeLength;
- }
- - (void)dealloc {
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- - (void)setFrame:(CGRect)frame {
- [super setFrame:frame];
- [self didChangeFrame];
- }
- // For autolayout
- - (void)setBounds:(CGRect)bounds {
- [super setBounds:bounds];
- [self didChangeFrame];
- }
- - (void)didMoveToWindow {
- [super didMoveToWindow];
-
- if (self.window) {
- [self scrollLabelIfNeeded];
- }
- }
- #pragma mark - Properties
- - (UIScrollView *)scrollView {
- if (_scrollView == nil) {
- _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
- _scrollView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
- _scrollView.backgroundColor = [UIColor clearColor];
- [self addSubview:_scrollView];
- }
- return _scrollView;
- }
- - (void)setFadeLength:(CGFloat)fadeLength {
- if (_fadeLength != fadeLength) {
- _fadeLength = fadeLength;
- [self refreshLabels];
- [self applyGradientMaskForFadeLength:fadeLength enableFade:NO];
- }
- }
- - (UILabel *)mainLabel {
- return [self.labels firstObject];
- }
- - (void)setText:(NSString *)theText {
- [self setText:theText refreshLabels:YES];
- }
- - (void)setText:(NSString *)theText refreshLabels:(BOOL)refresh {
- // ignore identical text changes
- if ([theText isEqualToString:self.text])
- return;
- EACH_LABEL(text, theText)
- if (refresh)
- [self refreshLabels];
- }
- - (NSString *)text {
- return self.mainLabel.text;
- }
- - (void)setAttributedText:(NSAttributedString *)theText {
- [self setAttributedText:theText refreshLabels:YES];
- }
- - (void)setAttributedText:(NSAttributedString *)theText refreshLabels:(BOOL)refresh {
- // ignore identical text changes
- if ([theText.string isEqualToString:self.attributedText.string])
- return;
- EACH_LABEL(attributedText, theText)
- if (refresh)
- [self refreshLabels];
- }
- - (NSAttributedString *)attributedText {
- return self.mainLabel.attributedText;
- }
- - (void)setTextColor:(UIColor *)color {
- EACH_LABEL(textColor, color)
- }
- - (UIColor *)textColor {
- return self.mainLabel.textColor;
- }
- - (void)setFont:(UIFont *)font {
- if (self.mainLabel.font == font)
- return;
- EACH_LABEL(font, font)
- [self refreshLabels];
- [self invalidateIntrinsicContentSize];
- }
- - (UIFont *)font {
- return self.mainLabel.font;
- }
- - (void)setScrollSpeed:(float)speed {
- _scrollSpeed = speed;
- [self scrollLabelIfNeeded];
- }
- - (void)setScrollDirection:(CBAutoScrollDirection)direction {
- _scrollDirection = direction;
- [self scrollLabelIfNeeded];
- }
- - (void)setShadowColor:(UIColor *)color {
- EACH_LABEL(shadowColor, color)
- }
- - (UIColor *)shadowColor {
- return self.mainLabel.shadowColor;
- }
- - (void)setShadowOffset:(CGSize)offset {
- EACH_LABEL(shadowOffset, offset)
- }
- - (CGSize)shadowOffset {
- return self.mainLabel.shadowOffset;
- }
- #pragma mark - Autolayout
- - (CGSize)intrinsicContentSize {
- return CGSizeMake(0, [self.mainLabel intrinsicContentSize].height);
- }
- #pragma mark - Misc
- - (void)observeApplicationNotifications {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- // restart scrolling when the app has been activated
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(scrollLabelIfNeeded)
- name:UIApplicationWillEnterForegroundNotification
- object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(scrollLabelIfNeeded)
- name:UIApplicationDidBecomeActiveNotification
- object:nil];
- #ifndef TARGET_OS_TV
- // refresh labels when interface orientation is changed
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(onUIApplicationDidChangeStatusBarOrientationNotification:)
- name:UIApplicationDidChangeStatusBarOrientationNotification
- object:nil];
- #endif
- }
- - (void)enableShadow {
- _scrolling = YES;
- [self applyGradientMaskForFadeLength:self.fadeLength enableFade:YES];
- }
- - (void)scrollLabelIfNeeded {
- if (!self.text.length)
- return;
- CGFloat labelWidth = CGRectGetWidth(self.mainLabel.bounds);
- if (labelWidth <= CGRectGetWidth(self.bounds))
- return;
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(scrollLabelIfNeeded) object:nil];
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(enableShadow) object:nil];
- [self.scrollView.layer removeAllAnimations];
- BOOL doScrollLeft = (self.scrollDirection == CBAutoScrollDirectionLeft);
- self.scrollView.contentOffset = (doScrollLeft ? CGPointZero : CGPointMake(labelWidth + self.labelSpacing, 0));
- // Add the left shadow after delay
- [self performSelector:@selector(enableShadow) withObject:nil afterDelay:self.pauseInterval];
- // animate the scrolling
- NSTimeInterval duration = labelWidth / self.scrollSpeed;
- [UIView animateWithDuration:duration delay:self.pauseInterval options:self.animationOptions | UIViewAnimationOptionAllowUserInteraction animations:^{
- // adjust offset
- self.scrollView.contentOffset = (doScrollLeft ? CGPointMake(labelWidth + self.labelSpacing, 0) : CGPointZero);
- } completion:^(BOOL finished) {
- self->_scrolling = NO;
- // remove the left shadow
- [self applyGradientMaskForFadeLength:self.fadeLength enableFade:NO];
- // setup pause delay/loop
- if (finished) {
- [self performSelector:@selector(scrollLabelIfNeeded) withObject:nil];
- }
- }];
- }
- - (void)refreshLabels {
- __block float offset = 0;
- each_object(self.labels, ^(UILabel *label) {
- [label sizeToFit];
- CGRect frame = label.frame;
- frame.origin = CGPointMake(offset, 0);
- frame.size.height = CGRectGetHeight(self.bounds);
- label.frame = frame;
- // Recenter label vertically within the scroll view
- label.center = CGPointMake(label.center.x, roundf(self.center.y - CGRectGetMinY(self.frame)));
- offset += CGRectGetWidth(label.bounds) + self.labelSpacing;
- });
- self.scrollView.contentOffset = CGPointZero;
- [self.scrollView.layer removeAllAnimations];
- // if the label is bigger than the space allocated, then it should scroll
- if (CGRectGetWidth(self.mainLabel.bounds) > CGRectGetWidth(self.bounds)) {
- CGSize size;
- size.width = CGRectGetWidth(self.mainLabel.bounds) + CGRectGetWidth(self.bounds) + self.labelSpacing;
- size.height = CGRectGetHeight(self.bounds);
- self.scrollView.contentSize = size;
- EACH_LABEL(hidden, NO)
- [self applyGradientMaskForFadeLength:self.fadeLength enableFade:self.scrolling];
- [self scrollLabelIfNeeded];
- } else {
- // Hide the other labels
- EACH_LABEL(hidden, (self.mainLabel != label))
- // adjust the scroll view and main label
- self.scrollView.contentSize = self.bounds.size;
- self.mainLabel.frame = self.bounds;
- self.mainLabel.hidden = NO;
- self.mainLabel.textAlignment = self.textAlignment;
- // cleanup animation
- [self.scrollView.layer removeAllAnimations];
- [self applyGradientMaskForFadeLength:0 enableFade:NO];
- }
- }
- // bounds or frame has been changed
- - (void)didChangeFrame {
- [self refreshLabels];
- [self applyGradientMaskForFadeLength:self.fadeLength enableFade:self.scrolling];
- }
- #pragma mark - Gradient
- // ref: https://github.com/cbpowell/MarqueeLabel
- - (void)applyGradientMaskForFadeLength:(CGFloat)fadeLength enableFade:(BOOL)fade {
- CGFloat labelWidth = CGRectGetWidth(self.mainLabel.bounds);
- if (labelWidth <= CGRectGetWidth(self.bounds))
- fadeLength = 0;
- if (fadeLength) {
- // Recreate gradient mask with new fade length
- CAGradientLayer *gradientMask = [CAGradientLayer layer];
- gradientMask.bounds = self.layer.bounds;
- gradientMask.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- gradientMask.shouldRasterize = YES;
- gradientMask.rasterizationScale = [UIScreen mainScreen].scale;
- gradientMask.startPoint = CGPointMake(0, CGRectGetMidY(self.frame));
- gradientMask.endPoint = CGPointMake(1, CGRectGetMidY(self.frame));
- // setup fade mask colors and location
- id transparent = (id)[UIColor clearColor].CGColor;
- id opaque = (id)[UIColor blackColor].CGColor;
- gradientMask.colors = @[transparent, opaque, opaque, transparent];
- // calcluate fade
- CGFloat fadePoint = fadeLength / CGRectGetWidth(self.bounds);
- NSNumber *leftFadePoint = @(fadePoint);
- NSNumber *rightFadePoint = @(1 - fadePoint);
- if (!fade) switch (self.scrollDirection) {
- case CBAutoScrollDirectionLeft:
- leftFadePoint = @0;
- break;
- case CBAutoScrollDirectionRight:
- leftFadePoint = @0;
- rightFadePoint = @1;
- break;
- }
- // apply calculations to mask
- gradientMask.locations = @[@0, leftFadePoint, rightFadePoint, @1];
- // don't animate the mask change
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- self.layer.mask = gradientMask;
- [CATransaction commit];
- } else {
- // Remove gradient mask for 0.0f length fade length
- self.layer.mask = nil;
- }
- }
- #pragma mark - Notifications
- - (void)onUIApplicationDidChangeStatusBarOrientationNotification:(NSNotification *)notification {
- // delay to have it re-calculate on next runloop
- [self performSelector:@selector(refreshLabels) withObject:nil afterDelay:.1f];
- [self performSelector:@selector(scrollLabelIfNeeded) withObject:nil afterDelay:.1f];
- }
- @end
|