Im building some sort of a scroll label, which basically is a UIView containing a label that i animate, causing the child Label to go back and forth if the text is larger than the UIView;
@implementation ScrollLabel
static float DEFAULT_SPEED_IN_PIXELS_PER_SECOND = 30.0;
static float DEFAULT_ANIMATION_DELAY = 2.0;
#pragma mark - Properties
#pragma mark - Initialization and Memory Management
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
label1 = [[UILabel label] retain];
[label1 setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[label1 setBackgroundColor:[UIColor clearColor]];
[label1 setNumberOfLines:1];
[self addSubview:label1];
speedInPixelsPerSecond = DEFAULT_SPEED_IN_PIXELS_PER_SECOND;
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
scrollType = theScrollType;
//[self setBackgroundColor:[UIColor greenColor]];
[self setClipsToBounds:YES];
return self;
}
- (void)dealloc
{
[label1 release];
[super dealloc];
}
#pragma mark - Public Static Methods
#pragma mark - Public Instance Methods
// Implement this method if you need more precise control over the layout of your subviews than the autoresizing behaviors provide.
- (void)layoutSubviews
{
[super layoutSubviews];
[self setLabelSize];
[self checkToStartOrStopAnimating];
}
- (void)setText:(NSString *)text
{
if( text == [label1 text] ) return;
[self stopAnimating];
[label1 setText:text];
[self setNeedsLayout];
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
}
- (void)checkToStartOrStopAnimating
{
if ([self shouldAnimate])
{
[self startAnimating];
}
else
{
[self stopAnimating];
}
}
- (void)setLabelSize
{
[label1 sizeToFit];
[label1 setFrame:CGRectMake(0, 0, [label1 frame].size.width, [label1 frame].si开发者_Go百科ze.height)];
}
- (void)startAnimating
{
if (!animating)
{
animating = YES;
[self animateForwards];
}
}
- (void)stopAnimating
{
if(animating)
{
animating = NO;
[[label1 layer] removeAllAnimations];
}
}
- (BOOL)isAnimating
{
return animating;
}
#pragma mark - Private Methods
- (void)animateForwards
{
float distanceToTravel = [label1 frame].size.width - [self frame].size.width;
if(distanceToTravel > 0 && animating )
{
CGRect rect = [label1 frame];
[UIView animateWithDuration:distanceToTravel / speedInPixelsPerSecond
delay:scrollAnimationDelay
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear
animations:^(void)
{
[label1 setFrame:CGRectMake(rect.size.width - self.size.width, 0, rect.size.width, rect.size.height)];
}
completion:^(BOOL finished)
{
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
if(finished)
{
[self animateBackwards];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}
- (void)animateBackwards
{
float distanceToTravel = [label1 frame].size.width - [self frame].size.width;
if (distanceToTravel > 0 && animating)
{
CGRect rect = [label1 frame];
[UIView animateWithDuration:distanceToTravel / speedInPixelsPerSecond
delay:scrollAnimationDelay
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear
animations:^(void)
{
[label1 setFrame:CGRectMake(0, 0, rect.size.width, rect.size.height)];
}
completion:^(BOOL finished)
{
if (finished)
{
[self animateForwards];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}
- (BOOL) shouldAnimate
{
return [label1 frame].size.width > [self frame].size.width && [self frame] > 0.0;
}
@end
Except when the animation is running (that is when the text takes up more space than the container view is wide), if you change the text to something else that also takes up more space than the container view is wide, somehow the thing is regressing into some state of flux in which all of the animations end with finish = NO (even though the label and parent view has correct size), which of course in turn causes layoutSubviews to be called, which in turn causes the animation to start again and end again with finish = NO. Note that this doesn't happen when the text actually fits in the frame of the parent.
Im pretty much clueless to why it happens; i can obviously guess that something isn't properly set after setting the text, but i can't figure out what that something is. Asked in a more general form, but of course no conclusive answers.
I solved the problem; apparently when the text got set, the animation stopped and layoutSubViews
called properly, while the stopping animation would go finished = NO
and thus call layoutSubViews
again restarting the animation. Solution is to check if an animation is going, and only call layoutSubviews
when no animation is currently running, else rely on the animation to cancel and that to call layoutSubviews
.
@implementation ScrollLabel
static float DEFAULT_SPEED_IN_PIXELS_PER_SECOND = 30.0;
static float DEFAULT_ANIMATION_DELAY = 2.0;
#pragma mark - Properties
#pragma mark - Initialization and Memory Management
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
label1 = [[UILabel label] retain];
[label1 setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[label1 setBackgroundColor:[UIColor clearColor]];
[label1 setNumberOfLines:1];
[self addSubview:label1];
speedInPixelsPerSecond = DEFAULT_SPEED_IN_PIXELS_PER_SECOND;
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
scrollType = theScrollType;
//[self setBackgroundColor:[UIColor greenColor]];
[self setClipsToBounds:YES];
return self;
}
- (void)dealloc
{
[label1 release];
[super dealloc];
}
#pragma mark - Public Static Methods
#pragma mark - Public Instance Methods
// Implement this method if you need more precise control over the layout of your subviews than the autoresizing behaviors provide.
- (void)layoutSubviews
{
[super layoutSubviews];
[self setLabelSize];
[self checkToStartOrStopAnimating];
}
- (void)setText:(NSString *)text
{
if( text == [label1 text] ) return;
[label1 setText:text];
if(animating)
{
[self stopAnimating];
}
else
{
[self setNeedsLayout];
}
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
}
- (void)checkToStartOrStopAnimating
{
if ([self shouldAnimate])
{
[self startAnimating];
}
else
{
[self stopAnimating];
}
}
- (void)setLabelSize
{
[label1 sizeToFit];
[label1 setFrame:CGRectMake(0, 0, [label1 frame].size.width, [label1 frame].size.height)];
}
- (void)startAnimating
{
if (!animating)
{
animating = YES;
[self animateForwards];
}
}
- (void)stopAnimating
{
if(animating)
{
animating = NO;
[[label1 layer] removeAllAnimations];
}
}
- (BOOL)isAnimating
{
return animating;
}
#pragma mark - Private Methods
- (void)animateForwards
{
float distanceToTravel = [label1 frame].size.width - [self frame].size.width;
if(distanceToTravel > 0 && animating )
{
CGRect rect = [label1 frame];
[UIView animateWithDuration:distanceToTravel / speedInPixelsPerSecond
delay:scrollAnimationDelay
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear
animations:^(void)
{
[label1 setFrame:CGRectMake(rect.size.width - self.size.width, 0, rect.size.width, rect.size.height)];
}
completion:^(BOOL finished)
{
scrollAnimationDelay = DEFAULT_ANIMATION_DELAY;
if(finished)
{
[self animateBackwards];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}];
}
else
{
[self stopAnimating];
}
}
- (void)animateBackwards
{
float distanceToTravel = [label1 frame].size.width - [self frame].size.width;
if (distanceToTravel > 0 && animating)
{
CGRect rect = [label1 frame];
[UIView animateWithDuration:distanceToTravel / speedInPixelsPerSecond
delay:scrollAnimationDelay
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear
animations:^(void)
{
[label1 setFrame:CGRectMake(0, 0, rect.size.width, rect.size.height)];
}
completion:^(BOOL finished)
{
if (finished)
{
[self animateForwards];
}
else
{
[self stopAnimating];
[self setNeedsLayout];
}
}];
}
else
{
[self stopAnimating];
}
}
- (BOOL) shouldAnimate
{
return [label1 frame].size.width > [self frame].size.width && [self frame] > 0.0;
}
@end
精彩评论