I am working with MapKit and annotations in iOS. Right before the annotations are added (which can take a while), I would like to display a small "Loading..." UIView. If I do this without threading, the loading view lags so much that it barely appears before it's dismissed. If I use performSelectorInBackground to load the annotations, it will work every few tries and the rest of the UIView will appear but no annotations, even though mapView:didAddAnnotationViews: is called. Can anyone think of why this would be behaving so unpredictably?
This is how I'm calling it, if it helps:
[self performSelectorInBackground:#selector(refreshAnnos) withObject:nil];
You can load the annotations on the background, but you should always ADD them on the main thread.
That is why
mapView:didAddAnnotationViews:
is called, but does not propagate to the UI. It would be the same as calling the UITableViewDelegate methods in the background.
You could possibly try the following design pattern using GCD
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load the annotations on the background queue;
dispatch_async(dispatch_get_main_queue(), ^{
//add the annotations to the mapView;
});
});
What do you do in refreshAnnos?
If you're adding the annotations to the mapview there, it will not work (because of the background thread). You need to update the mapview inside of the main thread.
Best,
Christian
Related
I have a view controller class that parses a clip's frames and renders it into an NSOpenGLView. Problem is, the frames being rendered are choppy as it seems the view controller is telling the view to draw faster than it could handle. See code snippet below for reference:
// Snippet code from view controller
- (void) tellViewToDrawWithFrameAtTimeInterval:(float)timeVal
{
_myGlobalTimer = [NSTimer scheduledTimerWithInterval:timeVal
target:self
selector:#selector(triggerRenderAtTime)
userInfo:nil
repeats:YES];
}
- (void) triggerRenderAtTime
{
[_myView setPixelData:[_myPixelBuffer objectAtIndex:_frameIndex];
[_myView setNeedsDisplay:YES];
_frameIndex++;
}
// The NSTimer object is eventually killed elsewhere in the class' implementation
I even tried to set the timer to call at intervals of 3, 5, 10 seconds just to ensure the view has time to process everything and render the frame. However, the results observed was still the same - the view is still skipping frames that it should be drawing. I'm currently looking at CVDisplayLink, but I'm very unfamiliar as to what they do yet, and based on what I've read so far, it seems to be a complex solution on an otherwise simple problem (I think...). Can anyone shed some light to this? Thanks!
Okay, I figured it out, and I had to eliminate drawRect completely and use CVDisplayLink for my drawing. Apple's guide was perfect for this:
Technical Q&A QA1385 - Driving OpenGL Rendering Loops
I have a Cocoa app that draws a lot data to the main screen(31000 samples by about 315 channels) so we are being very studious about profiling and getting everything as efficient as possible. I have a window controller that when opened updates it's view every 2 seconds based on the data. I am using an NSTimer and specifying the view update method.
The problem I am having is every time the timer fires the method, the main display hiccups slightly. I thought it would just be a matter of optimizing the drawRect method in the view subclass, but when I could not find any specific area in the draw rect method where the performance was bad, I decided to try commenting out the contents of the drawRect method.
results:
If I comment out the contents of the drawRect method, I will still get a hiccup.
If I comment out the call to [view setNeedsDisply: YES] in the calling method, it resolves the hiccup.
What Ive Tried:
1) I modified the method calls so that when the timer fired I was using performSelectorOnMainThread to call the view
2) I then tried to use the main dispatch queue with async.
neither of these things worked.
There is some kind of lag happening here even when there is no drawing work to do.
Any help is appreciated.
In my code, I subclassed NSView and in its drawRect method, I am spawning three threads to perform the drawing.
-(void)drawRect:(NSRect)dirtyRect
{
[[self window] setAllowsConcurrentViewDrawing:YES];
[self setCanDrawConcurrently:YES];
[NSThread detachNewThreadSelector:#selector(DrawText) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawRectangle) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawGradient) toTarget:self withObject:nil];
//Wherease these functions DrawText, DrawRectangle and DrawGradient performs their task as suggested by name.
//In DrawText, DrawRectangle, and DrawGradient lockFocus and unlockFocus is being
// used for multithreaded drawing.
}
When I run the same program from Xcode, it is running fine. Output is shown below.
But when I run it from the outside, there is problem and output is shown below.
First, I would like to know is it right way to draw from a secondary thread? Or what is another way to draw from a secondary thread?
What is the reason behind this problem?
Ken Aspeslagh is somewhat incorrect about drawing from secondary threads (he is correct it is generally a bad idea). From what I can see of your code you don't have a good use case for drawing on a secondary thread. Can you explain why you want to do this?
you yourself have already discovered setCanDrawConcurrently: which explicitly talks of invoking drawRect: from a background thread. Note that the views window must have allowsConcurrentViewDrawing set to YES for this to work (it is the default).
Apples own Cocoa Drawing Guide has a section on drawing from secondary threads. I have highlighted some portions I think are relevant to you.
The Application Kit maintains a unique graphics context for each window and thread combination. Because each thread has its own graphics context object for a given window, it is possible to use secondary threads to draw to that window. There are some caveats, however.
During the normal update cycle for windows, all drawing requests are sent to your application’s main thread for processing. The normal update cycle happens when a user event triggers a change in your user interface. In this situation, you would call the setNeedsDisplay: or setNeedsDisplayInRect: method (or the display family of methods) from your application’s main thread to invalidate the portions of your view that require redrawing. You should not call these methods from any secondary threads.
If you want to update a window or view from a secondary thread, you must manually lock focus on the window or view and initiate drawing yourself. Locking focus configures the drawing environment for that window's graphics context. Once locked, you can configure the drawing environment, issue your drawing commands as usual, and then flush the contents of the graphics context to the window buffer.
In order to draw regularly on a secondary thread, you must notify the thread yourself. The simplest way to send regular notifications is using an NSTimer or NSAnimation object. For more information on how to animate content, see “Advanced Drawing Techniques.”
The Cocoa Threading Programming Guide also says this:
If you want to use a thread to draw to a view, bracket all drawing code between the lockFocusIfCanDraw and unlockFocus methods of NSView
Just an aside, GCD block invocation is probably a much nicer method for performing small sets of operations in the background than NSThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// you can put each of these calls in their own queue if you want
[self DrawText];
[self DrawRectangle];
[self DrawGradient];
});
However, that likely has nothing to do with your problem; I mention it only because I think it will serve you better to use GCD queues.
You should only be drawing to the screen from the main thread.
Edit: It is apparently really complicated so you're better off drawing to the screen from the main thread. ;)
If you need to render something that takes too much time to avoid blocking the main thread, consider using a thread to do the drawing to an offscreen context, and then copy that context to the screen on the main thread.
I read about NSGraphicsContext Restriction at Thread guide.
Here, I found the following line:
If you do any drawing from a secondary thread, you must flush your drawing calls manually. Cocoa does not automatically update views with content drawn from secondary threads, so you need to call the flushGraphics method of NSGraphicsContext when you finish your drawing. If your application draws content from the main thread only, you do not need to flush your drawing calls.
After calling flushGraphics, it works fine.
As you know, Apple encourages us to use a new method called block-based animation about animation over iOS 4.0.
I really wonder what block-based animation is better than begin/end style animation.
performance?
concurrency?
coding efficiency and convenience?
I wondered about this too back then.
But after using block based animations like this:
[UIView animateWithDuration:0.5 ... ^{
// animated custom view vertically
} completion:^{
[UIView animateWithDuration:0.5 ... ^{
// animate the fade in alpha of buttons
}];
}];
It provides the completion handler in a short concise manner. You can also nest sub animation blocks within each other.
With BeginAnimation/EndAnimation, I don't recall exactly how to do a callback for completion handler, but you usually do something like:
// begin animation
// set delegate
// create delegate callback function for each beginAnimation
Now imagine if you wanted to nest 3 or 4 levels animation, such as as replicating the CSS Lightbox effect:
1) Fade in Lightbox container
2) Expand Width
3) Expand Height
4) Fade in form
You'd have to deal with some pretty messy if-else condition.
Your workflow would be like:
"After this beginAnimation finish, it sends a message to my callback method, scrolls down Xcode to find the callback delegate method, then in the callback method it calls another UIView beginAnimation, scroll back up Xcode to find the next beginAnimation ... "
With block based animation, each process is encapsulated in a block which you can nest in another block. If you decided you want to change the order things appear such that:
1) Fade in Lightbox container
2) Expand Height before Width this time
3) Expand Width after height this time
4) Fade in form
With the beginAnimation approach, you'll start pulling your hairs out.
Hope that helps.
Largely convenience.
There's little in the way of performance improvements that can be made from using a block. So it's unlikely anything in that. I'd have thought that all the block syntax for animations does is to call through to the old methods (or effectively do that through calling similar internal methods) and just run the block in between where you do beginAnimation and commitAnimation.
So, convenience. I suggest using it for that reason alone anyway. It's a lot easier to use and makes it easy to nest animations and do things on completion as you don't need to create other methods to call upon completion - it's just another block.
I'd like to create a View/Window that performs some inital loading in my application. I tried something like this:
StartWindow *start = [[StartWindow alloc] initWithNibName:#"Start" bundle:nil];
self.startWindow = start;
[start release];
[window addSubview:startWindow.view];
And in the viewDidLoad event inside StartWindow for the time being I just have [NSThread sleepForTimeInterval:3.0]; to simulate my loading.
The problem is that my view doesn't display until after the thread finished sleeping. why?
Edit
The above code is inside didFinishLaunchingWithOptions.
Because the framework is waiting for you to finish initialising the view in viewDidLoad. If you want loading to happen in the background you have to use some kind of background processing facility, like a separate thread or NSOperationQueue.
BTW, sleepForTimInterval doesn't actually run in a separate thread. It makes the calling thread sleep (in this case, the main UI thread).
The problem is that you block the main thread and thus the OS can't refresh the window and display your new view.
You could try to perform the loading in a second thread, or, if you need to call a lot of non threadsafe functions, you could start the loading after a short amount of time via an NSTimer so that the OS has time to refresh the window.
Another way is to perform the loading in viewDidAppear: which gets invoked when the view is displayed while viewDidLoad gets invoked when the view got loaded from the nib file.