NSAnimation looping - cocoa

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.

Related

Slow down or pause NSAnimationContext

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).

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.

CALayer Live Resize Poor Performance

I have a UI where the content of an NSCollectionViewItem's View is drawn programmatically through CALayers. I am using a CAConstraintLayoutManager to keep the layot of the sublayers consistent when resizing, but I am getting very poor performance when doing so. It seems that resizing the window, which causes the resize of two CATextLayers so that they fit the root layer's width, and the repositioning of one CATextLayer so that it stays right-aligned, is causing the application to spend most of its time executing the CGSScanConvolveAndIntegrateRGB function (I have used the Time Profiler instrument).
The most "expensive" layer (the one that causes the most stuttering even if it's the only one displayed) is a wrapped multiline CATextLayer. I have absolutely no idea how to get better performance (I have tried not using a CAConstraintLayoutManager and going with layer alignments but I'm getting the same thing). Has anyone had this problem? Is there a way around it?
PS: I have subclassed the layout manager and disabled all the animations during the execution of - (void)layoutSublayersOfLayer:(CALayer *)layer by setting YES to kCATransactionDisableActions in the CATransaction but it doesn't seems to help.
Edit: I have disabled Font Smoothing for the Text Layers and performance has increased a little bit (very little), but it spends an awful amount of time in _ZL9view_drawP7_CAViewdPK11CVTimeStampb (which is something that gets called by a thread of the ATI Radeon driver, I suppose).
I solved it. Kind of. It still seems like a dirty hack to me, but I couldn't find out how to make setNeedsDisplayInRect work so I ended up doing it like this:
In the NSWindow delegate:
-(void)windowWillStartLiveResize:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"beginResize" object:nil];
}
-(void)windowDidEndLiveResize:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"endResize" object:nil];
}
In my Custom View those two notifications call, respectively, the -(void)beginResize and -(void)endResize selectors. The first one sets a BOOL inLiveResize variable to YES, while the second one sets it to NO and calls setFrameSize again with the new frame size.
I overrode (overridden? Not native english speaker, sorry) the -(void)setFrameSize:(NSSize)newSize method like this:
-(void)setFrameSize:(NSSize)newSize
{
if (inLiveResize) {
NSRect scrollFrame = [[[self superview] enclosingScrollView] documentVisibleRect];
BOOL condition1 = (self.frame.origin.y > (scrollFrame.origin.y - self.frame.size.height));
BOOL condition2 = (self.frame.origin.y < (scrollFrame.origin.y + scrollFrame.size.height + self.frame.size.height));
if (condition1 && condition2)
[super setFrameSize:newSize];
}
else {
[super setFrameSize:newSize]; }}
That's it. This way, only the visible views resize live with the window, while the others get redrawn at the end of the operation. It works, but I don't like how 'dirty' it is, I'm sure there is a more elegant, built-in(ish) way to do this by using the setNeedsDisplayInRect method. I will research more.

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