On Wed, Feb 16, 2011 at 7:49 PM, Matt Neuburg <m...@tidbits.com> wrote: > On Tue, 15 Feb 2011 14:47:26 +0100, Florian Ebeling > <florian.ebel...@gmail.com> 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 florian.ebel...@gmail.com _______________________________________________ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) 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: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com