Why does viewDidAppear in UITabBarController exec before the view appears? - iphone

I have a UITabBarController that nests a UIView-Subclass (ImageViewer) as it's third tab.
In this ImageViewer Subclass I call the viewDidAppear method:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
/* ... start custom code ...
NSLog(#"viewDidAppear tag 1 passed); /* BREAKPOINT 1 here
[myUIActivityIndicator stopAnimating];
NSLog(#"viewDidAppear tag 2 passed); /* BREAKPOINT 2 here
/* ... end custom code ...
}
the method is called automatically, but strangely the view only appears after this method has been processed completely?
When I set breakpoints (1 and 2) as indicated, the processing (upon selecting the tab) stops whilst the previous tab is still showing. Only when clicking continue after the second breakpoint, the view will be displayed. (FYI the NSLogs are carried out immeldiately).
In this case viewDidAppear behaves more like viewWillAppear ....
Any clues what might be going on?
Cheers

If you want to allow the screen to be re-drawn when your view loads, but to trigger some other updating code in -viewDidAppear:, use performSelector:withObject:afterDelay: like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:0.0];
}
…
- (void)updateUI
{
// Do your UI stuff here
}
When you do it this way, the current event loop will finish quickly, and UIKit will be able to re-draw the screen after your view has loaded. updateUI will be called in the next event loop. This is a good way to get snappy view transitions if you have to perform computationally intensive calculations or updates after a view has loaded.

From the sound of it, if you are actively calling the method, the device might not have time to actually display the view while it is running the "custom code" in your viewDidAppear method. I that case you should let the program call the viewDidAppear method itself.
Your program may also be working on other code which would slow down the appearance of the view, this can be solved using timers. i.e. instead of:
[self otherCode];
you would write:
[NSTimer scheduledTimerWithTimeInterval:.5
target:self
selector:#selector(otherCode)
userInfo:nil
repeats:NO];
you might want to try simply delaying your "custom code" with a timer in this way.

Related

Stop performing the animation in background thread and run loop

I run my animations in a UITAbleViewCell.
Each cell has its own animation and the cells are reusable.
I use [mView performSelectorInBackground:#selector(layoutSubview) withObject:nil];
There in the background thread I initiate the runLoop to perform tasks like this:
- (void)startAnimation
{
NSRunLoop *mLoop = [NSRunLoop currentRunLoop];
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
mRunLoop = YES;
while (mRunLoop == YES && [mLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]);
}
and stop it:
- (void)stopAnimation
{
if (![NSThread isMainThread]) {
[[NSThread currentThread] cancel];
}
mRunLoop = NO;
self.animationTimer = nil;
CFRunLoopStop(CFRunLoopGetCurrent());
}
I run into problems when I fast scroll through table, because on the first cell initiation I begin the animation, so the first runLoop call occures which performs a setNeedDisplay and all the methods from it. But before finishing the first runLoop cycle the cell disappears from the view and is already available for reuse. So I begin clearing it, while the cycle is still performing operations and here I meet situations like
message sent to deallocated instance
So could you please give me some hints of how should I correctly stop performing the operations in that thread? I mean if I want to realese for example an object, which is performing some actions how to immediately stop'em?
Hope I gave enough info.
Thanks
UPDATE: No ideas at all?
I'll take a completely different stab on it:
Get rid of the cell's timers and background threads altogether!
Animation is not something where NSTimer is a good fit in the first place and having multiple timers won't help much, either.
UITableView has a method visibleCells and a method indexPathsForVisibleRows. I'd suggest to use a single CADisplayLink — which is suited for animation, as it calls you back with the actual refresh rate of the display or a fraction thereof — in your tableview-controller and in the callback of that display-link iterate over the visible cells.
If you want to schedule the display-link on the run-loop of a secondary thread, feel free to do so, but I'd check if you can get away without extra threading first.
Some code:
#interface AnimatedTableViewController ()
#property (strong, nonatomic) CADisplayLink *cellAnimator;
- (void)__cellAnimatorFired:(CADisplayLink *)animator;
#end
#implementation AnimatedTableViewController
#synthesize cellAnimator = cellAnimator_;
- (void)setCellAnimator:(CADisplayLink *)animator
{
if (animator == cellAnimator_)
return;
[cellAnimator_ invalidate];
cellAnimator_ = animator;
[cellAnimator_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSCommonRunLoopModes];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.cellAnimator = [CADisplayLink displayLinkWithTarget:self selector:#selector(__cellAnimatorFired:)];
...
}
- (void)viewWillDisappear:(BOOL)animated
{
self.cellAnimator = nil;
...
[super viewWillDisappear:animated];
}
- (void)__cellAnimatorFired:(CADisplayLink *)animator
{
NSArray *visibleCells = [self.tableView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger unused, BOOL *stop){
[cell setNeedsDisplay];
}];
}
...
#end
NSTimer has a -cancel method that stops the timer from firing. Calling it in -prepareForReuse (and, for that matter, in -stopAnimation) may help.
However, this code looks rather dangerous. Nesting run loops like this is almost never a good idea—and moreover, as far as I can tell it's totally unnecessary. If you let -startAnimation return, your animation timer will still get run on the main run loop. And if you're doing it this way because there's some code after -startAnimation that you want to delay, you should restructure your code so this isn't needed.
(If you drop the runloop stuff in -startAnimation, don't stop the runloop in -stopAnimation either.)
Something like the approach danyowdee recommends would be even better, but at least get rid of this runloop stuff. It's just asking for trouble.
I think you can use this method for your problem
[NSObject cancelPreviousPerformRequestsWithTarget:yourTarget selector:aSelector object: anArgument];
I think that the best way to avoid that behavior is assigning the delegate that receives the cancel method in other class that won't be reused. For example, you can have a private array of instances that process all the cancel methods, each row mapped into an array element.
I recommend you the lazy tables example provided by Apple in Xcode documentation. It's a great example of how to load images asynchroniously in background with a table. I think that also it would be useful for you for the scrolling subjects (decelerating and paging).
Only one more consideration, i don't recommend messing up with several cfrunloopstop, test it hard!

how to display UIActivityIndicatorView BEFORE rotation begins

I'd like to display an activity indicator BEFORE the work undertaken by willAnimateRotationToInterfaceOrientation:duration: begins. Most of the time in my app, this work is quickly completed and there would be no need for an activity indicator, but occasionally (first rotation, i.e. before I have cached data, when working with a large file) there can be a noticeable delay. Rather than re-architect my app to cope with this uncommon case, I'd rather just show the UIActivityIndicatorView while the app generates a cache and updates the display.
The problem is (or seems to be) that the display is not updated between the willRotateToInterfaceOrientation:duration and the willAnimateRotationToInterfaceOrientation:duration: method. So asking iOS to show UIActivityIndicator view in willRotate method doesn't actually affect the display until after the willAnimateRotation method.
The following code illustrates the issue. When run, the activity indicator appears only very briefly and AFTER the simulateHardWorkNeededToGetDisplayInShapeBeforeRotation method has completed.
Am I missing something obvious? And if not, any smart ideas as to how I could work around this issue?
Update: While suggestions about farming the heavy lifting off to another thread etc. are generally helpful, in my particular case I kind of do want to block the main thread to do my lifting. In the app, I have a tableView all of whose heights need to be recalculated. When - which is not a very common use case or I wouldn't even be considering this approach - there are very many rows, all the new heights are calculated (and then cached) during a [tableView reloadData]. If I farm the lifting off and let the rotate proceed, then after the rotate and before the lifting, my tableView hasn't been re-loaded. In the portrait to landscape case, for example, it doesn't occupy the full width. Of course, there are other workarounds, e.g. building a tableView with just a few rows prior to the rotate and then reloading the real one over that etc.
Example code to illustrate the issue:
#implementation ActivityIndicatorViewController
#synthesize activityIndicatorView = _pgActivityIndicatorView;
#synthesize label = _pgLabel;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willRotate");
[self showActivityIndicatorView];
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willAnimateRotation");
[self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
}
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
NSLog(#"didRotate");
[self hideActivityIndicatorView];
}
- (void) simulateHardWorkNeededToGetDisplayInShapeBeforeRotation;
{
NSLog(#"Starting simulated work");
NSDate* date = [NSDate date];
while (fabs([date timeIntervalSinceNow]) < 2.0)
{
//
}
NSLog(#"Finished simulated work");
}
- (void) showActivityIndicatorView;
{
NSLog(#"showActivity");
if (![self activityIndicatorView])
{
UIActivityIndicatorView* activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self setActivityIndicatorView:activityIndicatorView];
[[self activityIndicatorView] setCenter:[[self view] center]];
[[self activityIndicatorView] startAnimating];
[[self view] addSubview: [self activityIndicatorView]];
}
// in shipping code, an animation with delay would be used to ensure no indicator would show in the good cases
[[self activityIndicatorView] setHidden:NO];
}
- (void) hideActivityIndicatorView;
{
NSLog(#"hideActivity");
[[self activityIndicatorView] setHidden:YES];
}
- (void) dealloc;
{
[_pgActivityIndicatorView release];
[super dealloc];
}
- (void) viewDidLoad;
{
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(50.0, 50.0, 0.0, 0.0)];
[label setText:#"Activity Indicator and Rotate"];
[label setTextAlignment: UITextAlignmentCenter];
[label sizeToFit];
[[self view] addSubview:label];
[self setLabel:label];
[label release];
}
#end
The app doesn't update the screen to show the UIActivityIndicatorView until the main run loop regains control. When a rotation event happens, the willRotate... and willAnimateRotation... methods are called in one pass through the main run loop. So you block on the hard work method before displaying the activity indicator.
To make this work, you need to push the hard work over to another thread. I would put the call to the hard work method in the willRotate... method. That method would call back to this view controller when the work is completed so the view can be updated. I would put show the activity indicator in the willAnimateRotation... method. I wouldn't bother with a didRotateFrom... method. I recommend reading the Threaded Programming Guide.
Edit in response to a comment: You can effectively block user interaction by having the willAnimateRotation... method put a non functioning interface on screen such as a view displaying a dark overlay over and the UIActivityIndicatorView. Then when the heavy lifting is done, this overlay is removed, and the interface becomes active again. Then the drawing code will have the opportunity to properly add and animate the activity indicator.
More digging (first in Matt Neuberg's Programming iPhone 4) and then this helpful question on forcing Core Animation to run its thread from stackoverflow and I have a solution that seems to be working well. Both Neuberg and Apple issue strong caution about this approach because of the potential for unwelcome side effects. In testing so far, it seems to be OK for my particular case.
Changing the code above as follows implements the change. The key addition is [CATransaction flush], forcing the UIActivityIndicatorView to start displaying even though the run loop won't be ended until after the willAnimateRotationToInterfaceOrientation:duration method completes.
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willRotate");
[self showActivityIndicatorView];
[CATransaction flush]; // this starts the animation right away, w/o waiting for end of the run loop
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willAnimateRotation");
[self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
[self hideActivityIndicatorView];
}
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
NSLog(#"didRotate");
}
Try performing you work on a second thread after showing the activity view.
[self showActivityIndicatorView];
[self performSelector:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.01];
Either execute the heavy lifting in a background thread and post the results in the foreground thread to update the UI (UIKit is only thread safe since iOS 4.0):
[self performSelectorInBackground:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil]
Or you can schedule the heavy lifting method to be executed after the rotation took place:
[self performSelector:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.4]
But these are only hacks and the real solution is to have proper background processing if your UI needs heavy processing to get updated, may it be in portrait or landscape. NSOperation and NSOperationQueue is a good place to start.

Run a method after init in cocos2d

I have a loading screen that I initialise with a label to display the loading progression.
I want to call my DataManager AFTER initialising the loading screen, and then call a method to switch scenes. Here is my code:
-(id) init {
if((self=[super init]))
{
loadingLabel = ....
[self addChild:loadingLabel];
/***** This is what I want to call after the init method
//DataManager loads everything needed for the level, and has a reference to the
//loading screen so it can update the label
[[DataManager sharedDataManager] loadLevel:#"level1" screen:self];
//this will switch the scene
[self finishLoading];
*****/
}
return self;
}
-(void) setLoadingPercentage:(int) perc {
//changes the label
}
-(void) finishLoading {
[[CCDirector sharedDirector] replaceScene:[Game node]];
}
So I can't call the datamanager in the init, because the label will not get updated as content gets loaded, and I can't switch scenes in the init method. So How do I run my datamanger and finish loading AFTER the init? My plan was to set a schedule at an interval of 1 second that does this, but it doesn't seem right to have to wait a second.
EDIT: another way I could do it is to schedule at every frame and ask the datamanager where its at... This seems better already, since the datamanager wouldn't need a reference to the loading screen.
Any ideas?
You can use performSelector:withObject:afterDelay: to force the execution of the specified selector during the next iteration of the current threads run loop:
[self performSelector:#selector(finishLoading) withObject:nil afterDelay:0.0f];
The above answer is correct, but you should use the Cocos2d way to schedule methods to run later:
[self schedule:#selector(finishLoading) interval:0.1];
-(void)finishLoading
{
[self unschedule:#selector(finishLoading)];
//Do your stuff
}

Adding A Visual Timer To A View, iPhone

I have a view (splash screen) which displays for two minutes:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[viewController showSplash];
}
- (void)showSplash // Show splash screen
{
UIViewController *modalViewController = [[UIViewController alloc] init];
modalViewController.view = modelView;
[self presentModalViewController:modalViewController animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:120.0];
}
I want to add a timer which counts down from 2 minutes to zero to this splash screen.
I imagine I will need to create another view containing the timer. Is this correct? How would I do this and add it to the splash screen, and how would I make the numbers in the timer be displayed on screen in white?
I know two minutes is a very long time to display a splash screen for... but I am just experimenting with various things, there are other things going on for the two minutes!
Many thanks,
Stu
Edit // Ok I now have this:
(.h)
NSTimer *timer5;
UILabel *countDown;
float timeOnSplash;
(.m)
- (void) updateLabel:(NSTimer*)theTimer
{
float timeOnSplash = timeOnSplash - 1;
countDown.text = [NSString stringWithFormat:#"%02d:%02d", timeOnSplash];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
timer5 = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateLabel:)
userInfo:nil
repeats:YES];
countDown.text = [NSString stringWithFormat:#"%02d:%02d", timeOnSplash];
}
I get the following uncaught exception when I run the code:
'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key countDown.'
Any ideas?
Edit 2 // Working now, for an excellent solution see TechZen's answer.
Many thanks to all!
NSTimers are unusual objects in that they don't attach to the object that creates them but to the applications NSRunLoop instances. If a timer is one shot, you don't have to retain any reference to it. You should start the timer and forget about it.
In your case you need two track two time intervals (1) the passing of each second so you can update the interface and (2) passing of the two minute interval total.
For (1) you should evoke a repeating timer of one second interval that calls a method in the modalviewcontroller that updates the interface. The best place to evoke the timer would be in the controlller's viewDidAppear method. For (2) you can have property of the controller that stores a value of 159 and then have the method called by the timer decrement it each time the method is called. When it reaches zero, it invalidates the timer.
You should be aware that timers are affected by how quickly the runloop processes all events. If you have intensive background processes that don't pause every few microseconds, the timer may fail to fire on time. If you run into this problem, you should consider creating a separate threads for the splash screen and the configuration.
I do have to wonder why you need display the splash screen for exactly two minutes.
As an aside, the iPhone Human Interface Guidelines expressly state that you should not use splash screens. They can cause your app to be rejected. Using a splash screen that hangs around to long gives the impression that the app or the phone as failed and Apple doesn't like that either.
If you have some heavy duty configuration to do before the app is usable, it is better to create an interface that shows the configuration in process. That way, it is clear that the app is working and not just hung.
Even better, because no one on the move wants to stare at a static iPhone app for two minutes, it's better to get your user started doing something in one thread while the app configures in another. For example, in some kind of url connection, you could start the user typing in some and address of some data while the app makes the connection. For a game, you could have the user select their user name, review high scores, view instructions etc.
You should remember in the design process that people use apps on the iPhone primarily because it saves them time. They don't have drag out a laptop or go to a computer to perform some task. Your app design should focus on getting the user's task performed as quickly as possible. That is true even in the case of a game.
Normally, I would warn against premature optimization but this is kind of big deal.
Edit01:
You want something like this in your splash screen controller.
#interface SplashScreenViewController : UIViewController {
NSInteger countDown;
IBOutlet UILabel *displayTimeLabel;
}
#property NSInteger countDown;
#property(nonatomic, retain) UILabel *displayTimeLabel;
#end
#import "SplashScreenViewController.h"
#implementation SplashScreenViewController
#synthesize countDown;
#synthesize displayTimeLabel;
-(void) viewDidAppear:(BOOL)animated{
countDown=120;
NSTimer *secTimer=[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateCountDown:) userInfo:nil repeats:YES];
}//------------------------------------viewDidAppear:------------------------------------
-(void) updateCountDown:(NSTimer *) theTimer{
NSInteger mins,secs;
NSString *timeString;
countDown--;
if (countDown>=0) {
mins=countDown/60;
secs=countDown%60;
displayTimeLabel.text=[NSString stringWithFormat:#"%02d:%02d",mins,secs];
} else {
[theTimer invalidate];
// do whatever you wanted to do after two minutes
}
}//-------------------------------------(void) updateCountDown------------------------------------
#end
-------
you can call some method every second with NSTimer. there you can change view of your modal ViewController(for example, change text on the label, which will show time). and when your method will be called for the 120 time, you just invalidate your timer
You can use an NSTimer as #Morion suggested. An alternative is the following:
In your #interface file, add the variable:
NSInteger countDown;
then in #implementation:
- (void)showSplash // Show splash screen
{
UIViewController *modalViewController = [[UIViewController alloc] init];
modalViewController.view = modelView;
[self presentModalViewController:modalViewController animated:NO];
countdown = 120;
[self performSelector:#selector(updateTime) withObject:nil afterDelay:1.0];
}
- (void)updateTime {
//decrement countDown
if(--countDown > 0){
//
// change the text in your UILabel or wherever...
//
//set up another one-second delayed invocation
[self performSelector:#selector(updateTime) withObject:nil afterDelay:1.0];
}else{
// finished
[self hideSplash];
}
}
Yes, one way would be to create a new view which has a transparent background ([UIColor clearColor]) and a label with white text. Just call [modalViewController.view addSubview:timerView] to add it as a subview/overlay.
I'm not sure how much help you need with actually creating the views and setting up the label etc.

iPhone - Splash Screen with progress bar

I tried to create a SplashView which display the Default.png in the background and a UIProgressBar in front. But the splash screen is not being updated...
Inside my view controller I load first the splash view with a parameter how many steps my initialisation has and then I start a second thread via NSTimer and after each initialisation step I tell the SplashView to display the new progress value.
All looks good in theory, but when running this app the progress bar is not being updated (the method of the splash screen receives the values, I can see it in the logs). I also tried to add usleep(10000); in between to give the view updates a bit time and also instead of using the progress bar I drew directly on the view and called [self setNeedsDisplay]; but all didn't work :/
What am I doing wrong?
Thanks for your help!
Tom
Here is some code:
SPLASHSCREEN:
- (id)initWithFrame:(CGRect)frame withStepCount:(int)stepCount {
if (self = [super initWithFrame:frame]) {
// Initialization code
background = [[UIImageView alloc] initWithFrame: [self bounds]];
[background setImage: [UIImage imageWithContentsOfFile: [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], #"Default.png"]]];
[self addSubview: background];
progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
[progressView setFrame:CGRectMake(60.0f, 222.0f, 200.0f, 20.0f)];
[progressView setProgress: 0.0f];
stepValue = 1.0f / (float)stepCount;
[self addSubview:progressView];
}
return self;
}
- (void)tick {
value += stepValue;
[progressView setProgress: value];
}
VIEWCONTROLLER:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
splashView = [[SplashView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 480.0f) withStepCount:9];
[self setView: splashView];
NSTimer* delayTimer;
delayTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(finishInitialization) userInfo:nil repeats:NO];
}
return self;
}
- (void)finishInitialization {
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// init done... set the right view and release the SplashView
}
As mentioned in another answer, for some finite amount of time, as your app is being launched, Default.png is displayed and you have no control over it. However, if in your AppDelegate, you create a new view that displays the same Default.png, you can create a seamless transition from the original Default.png to a view that you can add a progress bar to.
Now, presumably, you have created a view or similar and you are updating a progress bar every so often in order to give the user some feedback. The challenge here is that your view is only drawn when it gets called to do a drawRect. If, however, you go from AppDelegate to some initialization code to a viewcontroller's viewDidLoad, without the run loop getting a chance to figure out which views need to have drawRect called on, then your view will never display its status bar.
Therefore in order to accomplish what you want, you have to either make sure that drawRect gets called, such as by pushing off a lot of your initialization code into other threads or timer tasks, or you can force the drawing by calling drawRect yourself, after setting up contexts and such.
If you go with the background tasks, then make sure your initialization code is thread-safe.
Default.png is just a graphic, a static image shown while the application is launching. If you want to show further progress, you'll have to show everything at the applicationDidLaunch phase. Show your modal "Splash Screen" there first (Create a view controller, add its view as a subview of your main window) and dismiss it when you are done whatever additional loading you needed to do.
Also, you need to do update your progress bar in a seperate thread. Updating your GUI in the same thread where a lot of business is going on is (in my opinion, but I could be wrong) a bad idea.
The main thread is, as far as I know, the only one that can safely do GUI things, and its event loop (that is, the main application thread's) is the one that does the actual displaying after you've called -setNeedsDisplay. Spawn a new thread to do your loading, and update the progress on the main thread.