Slow down or pause NSAnimationContext - cocoa

I have an NSAnimationContext (just a scrolling view) that I would like to slow down and/or pause whenever the cursor enters the view. I have already implemented the detection for when this happens - now I just need to figure out how to slow down the animation that is already in process. I have figured out how to do this with CALayers - but I need to use the animator proxy unable to use several AppKit views within this animation so Core Animation will not work. Does anyone know how to do this? Is there a way to keep track of NSAnimationContexts and then change them later on?
Here is a subsection of my code, The first block is cyclicly called. Everytime one agnation completes the next will begin.
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
context.duration = pixels/speed;
[[currentTweetView animator] setFrame:endRect];
} completionHandler:^{
[currentTweetView removeFromSuperview];
currentTweetView = nil;
[self nextAnimationWithAnimationIndex:currentIndex];
}];
Here is the code in the mouseEntered: method. Whenever this is called, neither completionHandler is ever called and the app freezes.
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
[[[self.subviews objectAtIndex:0] animator] setFrame:finalRect];
context.duration = 100.0;
} completionHandler:^{
NSLog(#"done");
}];
Also, is there any way to end an NSAnimationContext early and not call the completion handler?

I think if you just set the property via the animator proxy again, under a different NSAnimationContext, it will replace the animation that was in progress. This would be analogous to retargeting the animation (e.g. to a new destination).

Related

ScreenSaverView with CAEmitterLayer choppy on second screen

Any ideas why a screen saver using just a plain ScreenSaverView subclass with a CAEmitterLayer sublayer would render fine on the primary screen and choppy (as if every 2. frame renders there..) on the secondary screen..?
This is my initialization code:
- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
CAEmitterLayer* emitterLayer = [MyEmitterFactory emitterLayer:self];
[self setWantsLayer:YES];
[self.layer addSublayer:emitterLayer];
[self setAnimationTimeInterval:1/2.0];
}
return self;
}
Everything else in this subclass is default (as provided by the Xcode template).
Funny enough, backingStoreType does sound like a good candidate to tweak in a ScreenSaverView subclass using CoreAnimation, alas all other modes except the default one are not to be used as per the docs..
(As the animation is powered by Core Animation it doesn't really matter what I put in setAnimationTimeInterval - or remove the call completely, as experiments have shown)
According to the documentation of NSView setWantsLayer:
To create a layer-hosting view, you must call setLayer: and supply your layer object before you call the setWantsLayer: method; the order of these method calls is crucial.
Furthermore: Which OS version is it? Does the choppiness also come up when the two displays are mirrored (or vice versa)?

Setting up Continuous rendering in a mac osx cocoa application using OpenGL

I'm starting to work on a 3D particle system editor and evolver. Ive done something similar in the past with OpenGL but this time I'm making a mac os x cocoa application. I just have a few questions regarding some code I keep running into on setting up OpenGL.
1) Why do I see a lot of people on the web using...
[self setNeedsDisplay:YES];
Is this the proper way to get OpenGL to render, I now understand it leads to drawRect being called, but is it the correct way?
2) Is drawRect the proper method I should be overriding for my render frame method?
Heres the code that I continue to run into on the web:
-(void) prepareOpenGL {
[[self window] makeFirstResponder:self];
glClearColor(1.0f, 1.0f, 0.0f, 10.f);
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0/60.0 target:self selector:#selector(idle:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)idle:(NSTimer *)timer {
if(![[NSApplication sharedApplication] isHidden])
[self setNeedsDisplay:YES];
}
-(void) drawRect:(NSRect)dirtyRect {
glClear(GL_COLOR_BUFFER_BIT);
}
You haven't indicated whether you will be drawing your OpenGL content within an NSOpenGLView or a CAOpenGLLayer. These two have slightly different ways of updating their content for display to the screen.
For an NSOpenGLView, you don't need to update the view within it's -drawRect: method. In fact, I think you won't want to trigger -setNeedsDisplay: to do a refresh of the NSView because of some overhead that might incur. In one of my applications, I use a CVDisplayLink to trigger updates at 60 FPS within my own custom rendering methods in an NSOpenGLView. None of these touch -drawRect:. Frames are presented to the screen upon calling [[self openGLContext] flushBuffer], not by forcing a redraw of the NSView.
CAOpenGLLayers are a little different, in that you override - drawInCGLContext:pixelFormat:forLayerTime:displayTime: with your custom rendering code. This method is triggered in response to a manual -setNeedsDisplay or by the CAOpenGLLayer itself if its asynchronous property is set to YES. It knows when it's ready to present new content by the boolean value you provide in response to -canDrawInCGLContext:pixelFormat:forLayerTime:displayTime:.
I've used both of these, and each has its advantages. CAOpenGLLayers make it much easier to overlay other UI elements on your OpenGL rendering, but their rendering methods can be difficult to get to work correctly from a background thread. NSOpenGLViews can be updated easily on a background thread using a CVDisplayLink, but are a bear to overlay content on.

NSAnimation looping

I've a view that starts an animation when the user drag files on it. The animation shows the files' relative icon (inside an NSImageView Ivar of the NSAnimation custom class) going bigger while fading.
If I loop the animation inside a standard loop like:
CustomAnimation *animation = [[CustomAnimation alloc] initWithDuration: 2.0 animationCurve: NSAnimationLinear]
NSimage *icon;
for (NSString *filename in filenames) {
icon = [[NSWorkspace sharedWorkspace] iconForFile: filename];
NSImageView *myImageView = [[NSImageView alloc] initWithFrame: theFrame];
[myImageView setImage: icon];
[animation setImageView: myImageView];
[animation startAnimation];
}
The loop is too fast and the animations looks like if it were fired almost together (obviously).
What do you think is the best approach to loop an animation several times controlling the delay between the start of one animation and the subsequent?
The reason why your animation blaze by (except perhaps the last one) is that at each iteration your are "overwriting" the previous animation with a new one.
This post on chaining core animations together should do the trick if the idea is to have each animation take the full 2s. You can event add gaps between the animations.
-- Edit --
#Richard that's my mistake, I've misread the question.
A look at the documentaton for NSAnimation suggests that you might use startWhenAnimation:reachesProgress: to chain your animations though.
You can use NSTimer for that purposes.

UIImageView/AVAudioPlayer synchronization

I have an UIView that possess an UIImageView as a subview. This image view is intended to display an animation (basically, with the startAnimating method). When the animation start, I also need to play a sound. For this purpose, I use AVAudioPlayer's prepareToPlay and play methods.
Problem I encounter is that the FIRST TIME the global animation (image animation itself + sound) is launched, the sound is systematically played before the image animation is actually started. Not weird at all considering there is no synchronisation whatsoever.
But how could this synchronization be achieved? Is there some sort of callback which could be used know when the image animation is playing and launch the sound play from there...
Or maybe coupling UIImageView and AVAudioPlayer is not a good idea at all?
Here is my current implementation :
- (void)playSample {
previewView_ = [[[PreviewView alloc] initWithFrame:topView.bounds
backgroundImages:backgroundAnimationImages
characterImages:characterAnimationImages] autorelease];
[previewView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[topView addSubview:previewView_];
[audioPlayer_ setDelegate:self];
[audioPlayer_ setVolume:1.0];
[previewView_ startPreview]; // This calls startAnimating on the UIImageView of previewView_
[audioPlayer_ playSound];
}
Maybe you could use:
[audioPlayer playAtTime:[audioPlayer
deviceCurrentTime] + someDelayTimeInterval]
I found the the audioPlayer (or prepareToPlay) was messing up with my display updates, so what I ended up doing was to create the audioPlayer for each sound in viewDidLoad: and play it in a method called in a background thread:
[self performSelectorInBackground:#selector(playAudioPlayer:)
withObject:self.audioPlayer];

How do you put a normal control into an NSView?

What I'm actually trying to do is put a WebKitView into a ScreenSaver (which inherits NSView). I'm totally new to MacOS X and Cocoa (but I'm very familiar with Objective-C and used some parts of GNUStep). Do I need some laying out? I want to show only one control in the whole NSView.
In your initWithFrame:isPreview: method, create a WebView in the usual way, then, send yourself an addSubview: message, passing the web view:
webView = [[WebView alloc] initWithFrame:frame];
[self addSubview:webView];
If you're wondering what rectangle you should pass as the WebView's frame, read the View Programming Guide. Also, don't forget to release the webView in dealloc (or autorelease it in initWithFrame:isPreview:).
Once you have your web view, you'll need to load a page into it. Set a suitably long animation interval (at least a couple of seconds), and load the page in animateOneFrame:
- (void) animateOneFrame {
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com/"]]];
}
You may also want to handle WebViewProgressFinishedNotification, and put off re-loading the web view until that happens (to compensate for slow or soaked connections). You'll do this with an instance variable, which you set to YES in both initWithFrame:isPreview: and your notification-handler method, and test and set to NO in animateOneFrame:
- (void) animateOneFrame {
if (hasFinished) {
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com/"]]];
hasFinished = NO;
}
}
[aScreenSaverView addSubview:aWebKitView];
But why add a UIWebView into a screen saver view when you can just make the UIWebView take up the full screen on its own? Introducing view hierarchies where they are not needed is not a good idea because it increases the processing needed to display the interface.
You can also not worry too much about your animation interval by calling
[self stopAnimation];
at the end of your animateOneFrame method.

Resources