I have an MAAttachedWindow (subclass of NSWindow), which contains a blank view (contentView), and some arbitrary subview (right now an NSImageView).
On load, I'm attempting to resize the window by 100px vertically, in an animated fashion.
This code block initializes my window and views:
_popoverContentView = [[NSView alloc] initWithFrame: aFrame];
NSImageView *img = [[NSImageView alloc] initWithFrame: aFrame];
[img setImage: [NSImage imageName: "#my_debug_image"]];
[img setAutoresizingMask: NSViewHeighSizable];
[_popoverContentView setAutoresizesSubviews: YES];
[_popoverContentView addSubview: img];
popover = [[MAAttachedWindow alloc] initWithView: _popoverContentView attachedToPoint: aPoint inWindow: nil onSide: MAPositionBottom atDistance: aDist];
And this code block is responsible for the animation:
NSRect newPopoverFrame = popover.frame;
NSRect newPopoverContentViewFrame = _popoverContentView.frame;
newPopoverFrame.size.height += 100;
newPopoverContentViewFrame.size.height += 100;
[_popoverContentView animator] setFrame: newPopoverContentViewFrame];
[[popover animator] setFrame: newPopoverFrame display: YES animate: YES];
Now this all works (almost) as expect, however as shown in this video, the animation is unreliable, shaky, and jumpy. I can't seem to pinpoint what in my code is causing this, or how to go about locking the image view into place.
I think the problem is that you're using the new(ish) animator proxy to animate the window's frame while also using the much older animate: parameter of NSWindow's setFrame:display:animate:, which uses the old NSViewAnimation API.
These two animation methods are probably conflicting as they try to animate the window simultaneously using different code paths.
You also need to wrap multiple calls to the animator proxy in [NSAnimationContext beginGrouping] and [NSAnimationContext endGrouping] if you wish the animations to be simultaneous.
Try doing this:
[NSAnimationContext beginGrouping];
[_popoverContentView animator] setFrame: newPopoverContentViewFrame];
[[popover animator] setFrame: newPopoverFrame display: YES animate:NO];
[NSAnimationContext endGrouping];
If that doesn't work, you could drop the use of the problematic setFrame:display:animate: method and just animate the position and size independently:
[NSAnimationContext beginGrouping];
[_popoverContentView animator] setFrame: newPopoverContentViewFrame];
[[popover animator] setFrameOrigin: newPopoverFrame.origin];
[[popover animator] setFrameSize: newPopoverFrame.size];
[NSAnimationContext endGrouping];
The animation context grouping will ensure that everything happens simultaneously.
Related
My NSProgressIndicator is animating in a view
when I switch to another view ,and switch back
my NSProgressIndicator disappear.
why?
Here is my code for switch to another view
- (void)switchToView:(NSView *)newView withAnimate:(BOOL)animate
{
if ([[rootView subviews] count] != 0)
{
[rootView setWantsLayer:YES];
[rootView displayIfNeeded];
NSTimeInterval duration = [[self window] animationResizeTime:newFrame];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.25];
[[rootView animator] replaceSubview:[[rootView subviews] objectAtIndex:0] with:newView];
[NSAnimationContext beginGrouping];
if (duration > 0.25) {
[[NSAnimationContext currentContext] setDuration:0.25];
}
else{
[[NSAnimationContext currentContext] setDuration:duration];
}
[[[self window] animator] setFrame:newFrame display:YES animate:YES];
[NSAnimationContext endGrouping];
[NSAnimationContext endGrouping];
[self performSelector:#selector(endAnimation) withObject:nil afterDelay:0.25f];
}
}
-(void)endAnimation{
[[ _mainWindow contentView] setWantsLayer:NO];
}
I just replace a view,and make a fade out animation NSProgressIndicator animating all the time in a view.
I have solve this by myself ,I think it will be helpful for your guys.
I stop my NSProgressIndicator before I want to replace my view with NSProgressIndicator.
then I replace the view with a new view ,if I switch back I will start NSProgressIndicator with a - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay; method ,just set the delay=0 is OK.
In xib files , you should set NSProgressIndicator DisplayWhenStopped is YES.
And it will work well.
Then reason is after 10.6.5,At least on OS X 10.6.5 and above, as soon as you set an indetermined-progress-indicator's wantsLayer property to YES, the animation stops immediately .
So before it stop immediately ,I stop it by myself.And start it when I want to use it.
NOTICE: You should stop it by yourself better than system does, it can`t work well if system stop it .
I am trying to make a "hole" in an NSWindow using a CAShapeLayer or even just a CALayer.
When using regular NSViews or even layer-backed views, I can override drawRect: using code like this:
[spotImage drawInRect:self.bounds fromRect:NSZeroRect operation:NSCompositeXOR fraction:1.0];
where spotImage is an NSImage with pure white content and some gradations, and the window has a black background with 0.5 alpha. The NSView subclass where this drawRect is defined has a clearColor background.
The end result is a grey window (It is a transparent window with a styleMask of NSBorderlessWindowMask as can be found in many samples.
If I turn the NSView into a layer-backed view, it calls the drawRect methods and works fine.
When I turn this into a layer-hosting view, and again use the same structure (NSWindow > contentView > CustomView) then, the drawInRect method just draws the image. It no longer punches a hole through it.
It is like the layer itself can no longer punch the hole when it is part of a layer-hosting hierarchy.
Here is some sample code:
The custom NSWindow subclass initializer:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self) {
[self setBackgroundColor: [NSColor lightGrayColor]]; //[NSColor clearColor]];
[self setAlphaValue:0.5];
[self setOpaque:NO];
[self setHasShadow: NO];
[self useOptimizedDrawing:YES];
[self setIgnoresMouseEvents:YES];
}
return self;
}
the code in my applicationDidFinishLaunching method:
PPContentView *thisView = [[PPContentView alloc]
initWithFrame:CGRectInset([self.window.contentView bounds], 50, 50)];
//[thisView setWantsLayer:YES]; enabling this makes things opaque again
[self.window.contentView addSubview:thisView];
thisView.layer.backgroundColor = [NSColor clearColor].CGColor;
//Create custom content
[thisView setNeedsDisplay:YES];
}
and my custom view's drawRect contains:
[[NSImage imageNamed:#"spotFuzzy.png"] drawInRect:self.bounds fromRect:NSZeroRect operation:NSCompositeXOR fraction:1.0];
Here is my code snippet for cocoa application using core animation, somehow the animation doesn't show.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
[animation setDelegate:self];
NSRect pos = [imageView frame];
[animation setFromValue:[NSValue valueWithRect:pos]];
NSPoint point = NSMakePoint(pos.origin.x-40, pos.origin.y);
[animation setToValue:[NSValue valueWithPoint:point]];
[animation setDuration:2.0];
[[imageView animator] addAnimation:animation forKey:#"myTest"];
while this is the working code:
NSRect position = [imageView frame];
position.origin.x -= 40;
[[imageView animator] setFrame:position];
But autoReverse doesn't work.
Anything wrong with the first one? And how to make the reverse movement work in the 2nd one? Thanks!
I don't know for sure, but for the first one, position is a CGPoint, so you might try using that type for your fromValue and toValue. You're currently using an NSRect and an NSPoint (the latter of which should work, but not sure about the former).
For the second, how are you specifying auto-reverse? +setAnimationRepeatAutoreverses needs to be called from inside an animation block (after "beginAnimations")
(Note: I'm not very experienced with Cocoa since I'm chiefly an iOS developer; I haven't tested any of the following stuff)
I think the problem is that you're trying to mix CoreAnimation and Animator Proxy. You don't add the animation to the Animator, but to the layer:
[[imageView layer] addAnimation:animation forKey:#"myTest"];
Another possibility might be to use NSViewAnimation and chain them together. See the Animation Programming Guide for Cocoa, page 13. So you'd have one animation to go in one direction, and once it's finished it triggers the second one that goes back. It seems to work like this:
NSMutableDictionary *firstDict = [NSMutableDictionary dictionary];
[firstDict setObject:imageView forKey:NSViewAnimationTargetKey];
[firstDict setObject:[NSValue valueWithRect:originalFrame] forKey:NSViewAnimationStartFrameKey];
[firstDict setObject:[NSValue valueWithRect:targetFrame] forKey:NSViewAnimationEndFrameKey];
NSMutableDictionary *secondDict = [NSMutableDictionary dictionary];
[secondDict setObject:imageView forKey:NSViewAnimationTargetKey];
[secondDict setObject:[NSValue valueWithRect:targetFrame] forKey:NSViewAnimationStartFrameKey];
[secondDict setObject:[NSValue valueWithRect:originalFrame] forKey:NSViewAnimationEndFrameKey];
NSViewAnimation *firstAnimation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:firstDict]];
[firstAnimation setDuration:2.0];
NSViewAnimation *secondAnimation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:secondDict]];
[secondAnimation setDuration:2.0];
[secondAnimation startWhenAnimation:firstAnimation reachesProgress:1.0];
[firstAnimation startAnimation];
Then, in Lion (OS X 10.7) you can set a completion handler when using Animator Proxy. It should work like this:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:2.0];
[[NSAnimationContext currentContext] setCompletionHandler:^(void) {
// Here comes your code for the reverse animation.
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:2.0];
[[aView animator] setFrameOrigin:originalPosition];
[NSAnimationContext endGrouping];
}];
[[aView animator] setFrameOrigin:position];
[NSAnimationContext endGrouping];
I've got a problem handling the animators of NSViews.
In the code below I create a (custom) controller which also has got a view. I want it to fade it into the window, and fade the old one out. But it doesn't animate at all, the new view just appears, and the old one gets "removeFromSuperview" instantly.
Also, I have seen that the old view behaves normally, it fades out. But the new one is in the way and doesn't fade at all.
My code:
LTController *newController=[[LTController alloc] init]];
[[newController view] aFrame];
[[newController view] setAlphaValue:0];
[[[self window] contentView] addSubview:[newController view]];
[[[newController view] animator] setAlphaValue:1];
[[[viewController view] animator] setAlphaValue:0];
[viewController view] performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:[[NSAnimationContext currentContext] duration]];
[self setViewController:newController];
How can it be that the animator doesn't do anything to animate? What am I doing wrong?
Thanks in before,
Ivorius
You need to set your views as Layer backed for this to animate
I want to switch the image shown in an NSImageView, but I want to animate that change. I've tried various methods to do this. Hopefully one of you could suggest one that might actually work. I'm working with Cocoa for Mac.
As far as I know, NSImageView doesn't support animating image changes. However, you can place a second NSImageView on top of the first one and animate hiding the old one and showing the new one. For example:
NSImageView *newImageView = [[NSImageView alloc] initWithFrame: [imageView frame]];
[newImageView setImageFrameStyle: [imageView imageFrameStyle]];
// anything else you need to copy properties from the old image view
// ...or unarchive it from a nib
[newImageView setImage: [NSImage imageNamed: #"NSAdvanced"]];
[[imageView superview] addSubview: newImageView
positioned: NSWindowAbove relativeTo: imageView];
[newImageView release];
NSDictionary *fadeIn = [NSDictionary dictionaryWithObjectsAndKeys:
newImageView, NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
nil];
NSDictionary *fadeOut = [NSDictionary dictionaryWithObjectsAndKeys:
imageView, NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
nil];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:
[NSArray arrayWithObjects: fadeOut, fadeIn, nil]];
[animation setAnimationBlockingMode: NSAnimationBlocking];
[animation setDuration: 2.0];
[animation setAnimationCurve: NSAnimationEaseInOut];
[animation startAnimation];
[imageView removeFromSuperview];
imageView = newImageView;
[animation release];
If your view is big and you can require 10.5+, then you could do the same thing with Core Animation, which will be hardware accelerated and use a lot less CPU.
After creating newImageView, do something like:
[newImageView setAlphaValue: 0];
[newImageView setWantsLayer: YES];
// ...
[self performSelector: #selector(animateNewImageView:) withObject: newImageView afterDelay: 0];
- (void)animateNewImageView:(NSImageView *)newImageView;
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: 2];
[[newImageView animator] setAlphaValue: 1];
[[imageView animator] setAlphaValue: 0];
[NSAnimationContext endGrouping];
}
You'll need to modify the above to be abortable, but I'm not going to write all your code for you :-)
You could implement your own custom view that uses a Core Animation CALayer to store the image. When you set the contents property of the layer, the image will automatically smoothly animate from the old image to the new one.