UIImageView startAnimating blocking main thread even in NSOperation - animation

I am animating a series of PNG sequences of about 250 frames. I am doing this instead of running it as a H264 because I need background transparency.
For some reason even though I load my NSArray and load the startAnimating call inside of an NSInvocationOperation, the UI appears to freeze after startAnimating is called. I can see that while the images are being loaded into the array the app is still responsive. I am not sure how to stop startAnimating from blocking the main thread.
Thanks!

It is important that your call to startAnimating be done on the main thread - all UIKit activity must occur on the main threat. You can do that by changing your method call to something like this:
[imageView performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];

ended up switching to CoreMedia

Related

NSWindow won't draw immediately after app launch

The problem: Attempting to display a window with text from applicationWillFinishLaunching will NOT draw itself if other processor-intensive non-UI code is immediately called.
Background: I have a helper app that when launched may or may not interact with the end user. While it is "deciding" if it needs to put up a window to ask user questions, there may be anywhere from 1 second to 10 seconds that elapse (after launch it's off in non-UI capable library code communicating over the internet).
So I wanted to be kind to the user and put up a "mini-alert"* window with "working, please wait...", prior to heading into that library code, which I will dismiss once that processing has elapsed.
It seems as if the app itself doesn't have time after launch to even draw this mini-alert (it's just an NSWindow, with an NSView, some text, and no buttons).
If after the library code returns and want to put up either an error alert or a query window for the user -- then at that point the mini-alert draws as expected. However, if I close the mini-alert (see below) and then put up an NSAlert -- the mini-alert doesn't have enough time to dismiss itself.
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
[NSApp activateIgnoringOtherApps:YES];
briefAlertWindowController = [[NSWindowController alloc] initWithWindowNibName:#"BriefAlertWindow"];
[[briefAlertWindowController window] center];
[briefAlertWindowController showWindow:self ];
[[briefAlertWindowController window] orderFront:self ];
[[briefAlertWindowController window] display];
[[briefAlertWindowController window] makeKeyAndOrderFront:nil];
}
and dismissing the mini-alert:
- (void)dismissMiniAlert
{
NSWindow * theWindow = [briefAlertWindowController window];
[theWindow orderOut:nil];
}
NOTE that neither NSWindow not NSWindowController have been derived/subclassed for this mini-alert.
I'm using the term "mini-alert", because I've noticed people get annoyed about the concept of a "splash screen". While the functionality IS similar -- I'm really just trying to let the user know that an unavoidably long operation is taking place.
It sounds like a threading problem. The splash window can't draw itself on the main thread because the main thread is busy doing the processor-intensive operation. Properly, your processor-intensive stuff should all be happening on a background thread. If you can't do that, you need at least to get off the main thread long enough to give the runloop a chance to draw your window. Just introduce a delay.

Do UIKit objects need to dealloc on the main thread?

With respect to UIView, Apple has some comments on threading recommendations:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW147
Manipulations to your application’s user interface must occur on the
main thread. Thus, you should always call the methods of the UIView
class from code running in the main thread of your application. The
only time this may not be strictly necessary is when creating the view
object itself but all other manipulations should occur on the main
thread.
Is it generally (un)safe to dealloc a UIView object in a background thread? I'm looking for some supporting documentation in either case.
Yes, UIKit objects need to dealloc on the main thread. It is not safe to dealloc a UIView object in a background thread. See here for more details.
iOS Developer Library docs link to NSView Restrictions section so I guess they apply also to UIView.
NSView Restrictions
The NSView class is generally not thread-safe. You should create, destroy, resize, move, and perform other operations on NSView objects only from the main thread of an application. Drawing from secondary threads is thread-safe as long as you bracket drawing calls with calls to lockFocusIfCanDraw and unlockFocus.
Source:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123427
I hope this helps.

Layer backed NSOpenGLView + animation timer = strange drawing behavior?

I am creating an application which subclasses NSOpenGLView to do some OpenGL drawings. Now i wanted to add an overlay control, similar to QuickTime X. First thing i tried was setting NSOpenGLCPSurfaceOrder to -1 and making my window none-opaque. It did work, but i lost the windows shadow, so not a viable solution.
I then made my NSOpenGLView subclass layer-backed by calling [setWantsLayer:YES] and adding my control box as a subview. It worked, but i noticed a big drop in performance. I did some research and found this:
I am using an NSTimer object to call a method [timerFired:] 60 times per second. Works perfectly. The only thing i do within this method is calling [self setNeedsDisplay:YES], because when using a layer-backed OpenGLView one needs to overload the [drawRect:rect] method and do all the OpenGL drawing there. The problem: [drawRect:rect] often gets called although the timer didn't fire.
At first i thought "of course it does, it draws the NSOpenGLView, so it might be called by the window manager or something". So deleted my timer object to determine how it was called between [timerFired:] calls. The result: It wasn't called at all, at least not when not resizing or dragging the window.
So next i experimented with my timers time interval. Turns out, up until 55 times per second the [drawRect:rect] is called between 60 and 70 times per second, and from a timer interval of 59 times a second onward the [drawRect:rect] is called between 100 and 120 times a second.
I suspect that this highly unpredictable manner in which my drawing is called leads to the performance loss, either by being uneven or by clogging the thread with a lot of OpenGL drawings and not enough time to be executed or something. I also read that layer-backed OpenGLViews don't work with vsync, although i can't confirm this.
Does anyone have an explanation? Does anyone have an idea on how to only draw my OpenGL on timer fires?
I already tried the naive approach and added a boolean variable, set it true in my [timerFired:] and made my [drawRect:rect] only calling the OpenGL drawing method if the variable is true. The result was an extremely flickery and stuttery animation, so no luck.
What about using an CAOpenGLLayer with asynchronous animation oder CVDisplayLink? Would either help?
edit another thing which might be helpful information: I already used [setNeedsDisplay:YES] without my view being layer-backed, so i could easily switch between layer-backed and not-layer-backed, and i didn't have any of the problems described above, so it definitely has to do with my view being layer-backed. Everything gets a mess by just calling [setWantsLayer:YES].
So i replaced my NSTimer object with a CVDisplayLink and everything is smooth again. Although i don't actually understand why. It seems like requesting a backing layer broke the vertical sync, but it does not explain the random calls to drawRect that appeared out of nowhere.
Also i would really like to if vsync works with a layer backed NSOpenGLView and / or with a CAOpenGLLayer. I can't find any official information.

Continuos UI update Mac Application?

I want my Mac cocoa application to continuously update the View even if it goes to back ground? Something like Activity Monitor or DigitalColor Meter.
How can I achieve such behavior?
Thank you!
EDIT: I get context from NSGraphicContext and then create a thread to do the job in back ground.
- (void)drawRect:(NSRect)dirtyRect
{
context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
[NSThread detachNewThreadSelector: #selector(ExecuteDrawingInBackGround:) toTarget: self withObject: nil];
}
You can't use the Mac UI API from a background thread because it's not thread safe.
Programs that redraw themselves continuously do so by having the background threads (if any) notify the main thread that something has changed and needs redrawing. This is easiest done by having the background thread send -performSelector:OnMainThreadWithObject:waitUntilDone: to some object, usually the document or a view controller.
Alternatively, you can also use NSTimer to fire regularly and avoid background threads altogether.

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];

Resources