Re: Animation doesn't reach end value
Re: Animation doesn't reach end value
- Subject: Re: Animation doesn't reach end value
- From: Florian Ebeling <email@hidden>
- Date: Sun, 20 Feb 2011 20:35:09 +0100
On Wed, Feb 16, 2011 at 7:49 PM, Matt Neuburg <email@hidden> wrote:
> On Tue, 15 Feb 2011 14:47:26 +0100, Florian Ebeling <email@hidden> said:
>>I try to create a view which creates an effect that is similar to the
>>Find Indicator in NSTextView. To achieve that there is a view with
>>text which is animated using CAAnimation objects registered via
>>setAnimations:. The animated properties are frameSize, frameOrigin and
>>animatedFontSize, but the triggering property is the "hidden"
>>property. The whole effect works and looks almost as I imagined it,
>>but there is one problem: the properties do not reach the final value
>
> Show your code. That way, others can test, reproduce, consider. In your email as it stands there is nothing to respond to.
Uh, I managed to overlook your response. Here is the code:
#import "L40ErrorIndicator.h"
static NSColor *midRed;
static NSColor *lightRed;
static NSColor *darkRed;
@interface L40ErrorIndicator (L40PrivateMethods)
- (CGPathRef)frameSizeAnimationPath;
- (CGPathRef)fontSizeAnimationPath;
- (void)updateAnimations;
- (void)setupTextDrawing;
- (void)recalculateFrame;
- (void)updateTextStorageForSize:(CGFloat)fontSize;
- (NSSize) maximumScaledLabelSize;
@end
@implementation L40ErrorIndicator
@synthesize label;
@synthesize scale;
+ (void) initialize
{
midRed = [NSColor colorWithCalibratedHue:0.0 saturation:1.0
brightness:0.5 alpha:1.0];
lightRed = [NSColor colorWithCalibratedHue:0.0 saturation:1.0
brightness:0.7 alpha:1.0];
darkRed = [NSColor colorWithCalibratedHue:0.0 saturation:1.0
brightness:0.18 alpha:1.0];
}
- (id)init
{
return [self initWithBaseFont:[NSFont fontWithName:@"Futura Medium"
size:16.0f]];
}
- (id)initWithBaseFont:(NSFont *)f
{
return [self initWithBaseFont:f atPoint:NSZeroPoint];
}
- (id)initWithBaseFont:(NSFont *)f atPoint:(NSPoint)p
{
return [self initWithBaseFont:f atPoint:p label:@""];
}
- (id)initWithBaseFont:(NSFont *)f atPoint:(NSPoint)p label:(NSString *)aLabel
{
self = [super initWithFrame:NSZeroRect];
if (self) {
origin = p;
label = aLabel;
self.font = f;
scale = 1.6f;
margin = 6.0f;
[self setupTextDrawing];
[self recalculateFrame];
}
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)setupTextDrawing
{
textStorage = [[NSTextStorage alloc] initWithString:label];
layoutManager = [[NSLayoutManager alloc] init];
textContainer = [[NSTextContainer alloc] init];
[textContainer setLineFragmentPadding:0.0];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
}
- (void)setLabel:(NSString *)s
{
label = s;
[self recalculateFrame];
[self setNeedsDisplay:YES];
}
- (NSString *)label
{
return label;
}
-(void)setAnimatedFontSize:(CGFloat)p {
animatedFontSize = p;
[self setNeedsDisplay:YES];
}
-(CGFloat)animatedFontSize {
return animatedFontSize;
}
- (void)setFrameSize:(NSSize)size
{
[super setFrameSize:size];
[self setNeedsDisplay:YES];
}
- (void)setFrameOrigin:(NSPoint)p
{
[super setFrameOrigin:p];
[self setNeedsDisplay:YES];
}
- (NSFont *)font
{
return font;
}
- (void)setFont:(NSFont *)f
{
font = f;
self.animatedFontSize = [font pointSize];
[self recalculateFrame];
[self setNeedsDisplay:YES];
}
- (NSPoint)origin
{
return origin;
}
- (void)setOrigin:(NSPoint)p
{
origin = p;
[self recalculateFrame];
[self setNeedsDisplay:YES];
}
- (NSSize)frameSizeForLabelSize:(NSSize)labelSize
{
return NSMakeSize(labelSize.width + 2 * margin, labelSize.height + 2
* margin);
}
#pragma mark Animation
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)reachedEnd
{
NSArray *animations = ((CAAnimationGroup *)anim).animations;
CABasicAnimation *fontSizeAnim = [animations objectAtIndex:0];
CABasicAnimation *frameOriginAnim = [animations objectAtIndex:1];
CABasicAnimation *frameSizeAnim = [animations objectAtIndex:2];
self.animatedFontSize = [(NSNumber *)fontSizeAnim.fromValue floatValue];
[self setFrameOrigin:(NSPoint)[(NSValue *)frameOriginAnim.fromValue
pointValue]];
[self setFrameSize:(NSSize)[(NSValue *)frameSizeAnim.fromValue sizeValue]];
}
- (CABasicAnimation *)fontSizeAnimation
{
CABasicAnimation *animation = [CABasicAnimation
animationWithKeyPath:@"animatedFontSize"];
CGFloat fontSize = [font pointSize];
animation.fromValue = [NSNumber numberWithFloat:fontSize];
animation.toValue = [NSNumber numberWithFloat:fontSize * scale];
return animation;
}
- (CABasicAnimation *)frameSizeAnimation
{
CABasicAnimation *animation = [CABasicAnimation
animationWithKeyPath:@"frameSize"];
NSSize labelSize = [self maximumScaledLabelSize];
NSSize frameSize = [self frameSizeForLabelSize:labelSize];
animation.fromValue = [NSValue valueWithSize:self.frame.size];
animation.toValue = [NSValue valueWithSize:frameSize];
return animation;
}
- (CABasicAnimation *)frameOriginAnimation
{
CABasicAnimation *animation = [CABasicAnimation
animationWithKeyPath:@"frameOrigin"];
NSSize labelSize = [self maximumScaledLabelSize];
NSSize frameSize = [self frameSizeForLabelSize:labelSize];
CGFloat dw = frameSize.width - self.frame.size.width;
CGFloat dh = frameSize.height - self.frame.size.height;
animation.toValue = [NSValue
valueWithPoint:NSMakePoint(self.frame.origin.x - dw/2,
self.frame.origin.y - dh/2)];
animation.fromValue = [NSValue valueWithPoint:self.frame.origin];
return animation;
}
- (CAAnimationGroup *)animationGroup
{
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:
[self fontSizeAnimation],
[self frameOriginAnimation],
[self frameSizeAnimation],
nil];
group.duration = .1f;
group.autoreverses = YES;
group.delegate = self;
return group;
}
- (void)updateAnimations
{
[self setAnimations:[NSDictionary
dictionaryWithObjectsAndKeys:self.animationGroup, @"hidden", nil]];
}
#pragma mark Drawing
- (void) updateTextStorageForSize:(CGFloat)fontSize
{
if (attrs == NULL) {
attrs = [NSMutableDictionary dictionary];
[attrs setObject:darkRed forKey:NSForegroundColorAttributeName];
}
[textStorage beginEditing];
NSFont *scaledFont = [NSFont fontWithName:[font fontName] size:fontSize];
[attrs setObject:scaledFont forKey:NSFontAttributeName];
NSMutableAttributedString *attributedLabel =
[[NSMutableAttributedString alloc] initWithString:label
attributes:attrs] ;
[textStorage setAttributedString:attributedLabel];
[textStorage endEditing];
}
- (NSSize) maximumScaledLabelSize
{
[self updateTextStorageForSize:[font pointSize] * self.scale];
NSRange range = [layoutManager glyphRangeForTextContainer:textContainer];
NSSize labelSize = [layoutManager boundingRectForGlyphRange:range
inTextContainer:textContainer].size;
return labelSize;
}
// Recalculates geometry after change of properties: label, font, origin.
// Does not recalculate during animation.
- (void)recalculateFrame
{
[self updateTextStorageForSize:[font pointSize]];
glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSSize labelSize = [layoutManager
boundingRectForGlyphRange:glyphRange
inTextContainer:textContainer].size;
NSSize frameSize = [self frameSizeForLabelSize:labelSize];
[self setFrameOrigin:NSMakePoint(origin.x - margin, origin.y - margin)];
[self setFrameSize:frameSize];
[self updateAnimations];
}
- (void)drawRect:(NSRect)rect
{
[[NSGraphicsContext currentContext] saveGraphicsState];
CGFloat diameter = MAX([font pointSize], animatedFontSize);
NSBezierPath *path = [NSBezierPath
bezierPathWithRoundedRect:self.bounds xRadius:diameter/2
yRadius:diameter/2];
[path setLineJoinStyle:NSRoundLineJoinStyle];
CGFloat stroke = 3.0f;
[path setLineWidth:stroke];
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:lightRed endingColor:midRed];
[gradient drawInBezierPath:path angle:90];
// [darkRed set];
// [path stroke];
[self updateTextStorageForSize:MAX([font pointSize], animatedFontSize)];
NSPoint labelOrigin = NSMakePoint(margin, margin);
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:labelOrigin];
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
@end
It is working not, but I'm still curious if this is the right way to do it.
Remember, my problem was that the animation would stop somewhat short
of the target value (the fromValue, since it is an autoreverses=YES
setup).
The animation would successively set the properties to new values over
time, just as expected during animation. That part worked fine.
How I deal with it now is that I implement the delegate
animationDidStop:finshed: message, look into the animation and using
the target valus from there to set the final value.
I also managed to convince myself, that this is quite possibly the
appropriate solution to this problem, given that I'm operating one
level below Cocoa, using QuartzCore APIs. Would people with more
overview agree with that maybe?
Maybe the animator proxy works the same way: it would only handle the
animation for one property and deal with CABasicAnimation under the
hood, and probably feel responsible to act as delegate for the
animation as well. And in this role the animator proxy would also put
the target value. That would mean the animation would not do this in
the simplified animator proxy case either, consistent with what I saw.
These are just my assumptions. Would you agree?
An interesting question would be if there were easier, or more
high-level ways to achieve what I need: animating multiple properties
combined with chaining or autoreverse-animating them.
Florian
--
Florian Ebeling
email@hidden
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden