I have an app that starts with a tableview (from a xib) that loads multiple navigation controllers. I want to present a modal view on startup if a long init sequence is underway. I tried presenting a modal view in the App Delegate, but the view doesn't appear until well after the underlying code is already complete.
MainWindow.xib loads TableViewController, so I put my call to presentmodalview in that View Will Appear with the same result. I have numerous NSLOG calls so I can watch what is happening but I can't figure out why the view doesn't appear until after both the app delegate and the tableview controller's viewWillAppear finish. I moved the call to viewDidAppear with the same result. Here is a code snippet:
App Delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
[[UIApplication sharedApplication] setStatusBarHidden:NO];
// if new version being installed do init stuff
if ( <needs update code here>) {
Uncompress *uncompressView = [[Uncompress alloc] initWithNibName:#"Uncompress" bundle:nil];
[self.tabBarController presentModalViewController:uncompressView animated:NO];
[uncompressView release];
}
}
I also tried changing presentmodalviewcontroller to [window addSubview:uncompressView.view] without any luck.
The Update code runs just fine, the problem is the view doesn't appear until both the AppDelegate and the TableView are finished. I'm not creating any views programatically - all are from Xib's. I can't figure out where to call the update function to get the view to appear right away. Any help appreciated. Thank you!
On iOS, the UI is only updated when your code returns control to the run loop. So if your uncompress task takes a lot of time, the UI will only be updated after it has finished.
You can sort of work around this issue by putting the time-intensive task in a separate method and calling that method with [self performSelector:... afterDelay:0.0 ...]; but it is not a good solution because even if the UI will update, user interaction with your app will still be blocked as long as your code blocks the main thread. And if your code takes more than a few seconds to run (e.g. because it runs on an older, slower device), the OS watchdog timer will kill your app.
The best solution would be to put the time-intensive task in a background thread, e.g. with NSOperation or dispatch_async().
Related
I will like my application that always starts as the first time I open it. I have several view controllers and when I exit my application and I open it again I see the view controller where I left of. Maybe I have to call the method applicationWillTerminate method.
I use this code to open a new view:
UIViewController *control = [[SomeViewController alloc] initWithNibName:#"SomeViewController"
bundle:nil];
UINavigationController *navControl = [[UINavigationController alloc]
initWithRootViewController:control];
[self presentModalViewController:navControl animated:NO];
[navControl setNavigationBarHidden:YES];
[control release];
[navControl release];
this code works great when linking it to buttons. But when I place that code in the applicationDidBecomeActive method it does not work.
The easiest way is to set UIApplicationExitsOnSuspend in Info.plist.
That really isn't the expected behaviour, though. Users expect to see the app "where they left off", especially if they've only briefly left the app (e.g. because they got a phone call).
Your code snippet adds a view controller, but is unlikely to work since your app delegate is not a UIViewController. It also doesn't do anything about removing the old view controllers.
EDIT: If all you need to do is display a splash screen (or something), then it's something like this:
In -applicationDidEnterBackground:, add a "splash screen" view (not a view controller) to self.window. (iOS takes a "screenshot" after you return from -applicationDidEnterBackground: and uses this in the app-switch animation; you want this to be what the user sees when switching back to your app)
In -applicationWillEnterForeground:, do whatever animations you want and eventually remove the view from the window (call -removeFromSuperview).
EDIT 2: The same will work in -applicationWillResignActive:/-applicationWillBecomeActive:, except this happens on a sleep/wake event, which might not be what you want...
I'd avoid using view controllers for this, because trying to shoehorn a view controller in the view controller hierarchy is likely to be problematic (for example, you have to figure out which VC to present it from, and you have to do the "right thing" if the user backgrounds your app while the VC is on screen, and...)
The reason it doesn't work in applicationDidBecomeActive is that method is only sent to the Application delegate, which doesn't know about presentModalViewController.
I suggest instead that in your appDelegate, implement applicationWillEnterForeground:, which should restore the state to a newly launched application (equivalent to what the state is at the end of application:didFinishLaunchingWithOptions: ).
OR...(edits)
If you just want a certain viewController to run (which is still loaded, right?)...For example, if you have a tab controller and just want to go to the root of the first view controller, put the following code into applicationWillEnterForeground:
UITabBarController * myTabBar = self.tabBarController;
myTabBar.selectedIndex = 0;
[[myTabBar.viewControllers objectAtIndex:0] popToRootViewControllerAnimated:NO];
Temporary solution:
I made my application crash on applicationWillResignActive method and it works. My application needs to run an animation when I launch it. But this works because next time the application runs it starts like the first time I opened it.
it happens only on the 3GS, 4 or 3G is OK.
seems like viewcontroller got called everytime camera dismissed.
any thoughts on that?
It's Iphone, the system calls viewdidunload my view of viewcontroller when I did a
[self presentModalViewController:picker1 animated:YES];
I don't want the system to dismiss my view. I did a [self.view retain] but that doesn't help.
viewDidLoad can be called multiple times. If the iPhone needs memory, it will release the view and then rebuild it the next time it has to show it. This is normal -- you cannot count on only a single call. You should get a viewDidUnload call before it's called again.
I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.
The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.
You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.
In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.
You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.
maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.
This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think
https://github.com/defagos/CoconutKit/issues/4
If you have a line like this in your AppDelegate
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like
yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];
in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.
You definitely should provide more info.
Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?
Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.
I was calling:
[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];
from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:
Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:
Original viewDidAppear: called
Call presentViewController here
Original viewDidDisappear: called
New view is presented and no longer gives me a warning about "unbalanced VC display"
Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."
it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).
I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:
BOOL viewDidLoadProcessed = false;
-(void)viewDidLoad:(BOOL)animated
{
if (!viewDidLoadProcessed)
{
viewDidLoadProcessed = YES;
.
.
.
... do stuff here...
.
.
}
}
Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!
Kind Regards
Heider Sati
Adding [super viewDidAppear:animated]; worked for me:
//Called twice
- (void)viewDidAppear:(BOOL)animated{
}
//Called once
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
I load view controllers all the time in the following format:
-(void)loadSelectUser {
MyViewController *nextController = [[MyViewController alloc] initWithStyle:UITableViewStyleGrouped];
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:nextController animated:YES];
[nextController release];
}
And I've never had an issue with that. But right now I'm dealing with the issue that the next view doesn't load completely. The navigation bar shows up and the viewDidLoad and the numberOfSectionsInTableView methods are both called. That is it. The table doesn't show up, it still shows the previous view.
I imagine this means there is a memory leak or something not connected properly. Is this the right path to be looking? If so, what is your best suggestion for debugging this issue. My code has no error messages so I'm not sure where to start. I load the view properly in a different controller, but for some reason it doesn't do it after this particular view*.
*This view happens to do a lot data manipulation with downloading objects, saving them and such. But again, it looks like it is all working properly. What would mess up the navigation controller loading the next view completely?
Oh, and just to mess things up more, some times, it works properly. But I run it one more time and it doesn't do it again.
Update: TechZen comment about the proper way to push a new view controller seemed to help a little. There is a higher rate of it working, unless I am pushing a tableviewcontroller. Depending on the action my view will push a UITableViewController or a UIViewController with a nib file. The second usually (not always) works.
Also, in a different view I am adding a modal view. But when I try to dismiss it using [self dismissModalViewControllerAnimated:YES]; it doesn't always work. Again it's hit or miss on it. Anyone have an idea of what would be causing the transition of windows to be finicky?
Calling the app delegate to get the navigation controller is unnecessary and risky. Any view controller on a navigation controller stack has a populated navigationController property so you can just use self.navigationController.
It's risky to call the app delegate's navigation controller because you have no guarantee that you will get the same navigation controller as the one that currently holds the view controller calling the push. You could in theory end up with two overlapping and conflict navigation controllers.
Switch the code to self.navigationController and see if that fixes the problem.
I'm writing a 'load' feature for my current iPhone app.
Practically what I want to do is get the user back where s/he left off. To do so I want to recreate all the UIViewControllers appeared before.
My problem is the UIViewControllers won't appear if I simply call [[self navigationController] pushViewController:newController animated:YES];.
The same code works fine when invoked as an event handler, like after touching a button. But if I do same without an even (for ex. in viewDidLoad) the new view controller's loadView method won't get called.
My actual code (with some pseudo elements):
- (void)viewDidLoad {
[super viewDidLoad];
if (loading)
[self onSomeEvent];
}
- (void)onSomeEvent {
UIViewController *newController = //init....;
[[self navigationController] pushViewController:newController animated:YES];
[newController release];
}
I guess viewDidLoad is not the right place to do such a call, but then what is?
I'm not sure that spreading your "load" feature all accross your controllers is the best way to achieve it.
I would rather put it in the init of your application, in the applicationDidFinishLauching part. Then you have a centralized place where you restore the previous state (easier to handle IMHO).
Let's suppose you want to implement some more advanced restore feature like:
displaying a splash UIView with an activity indicator to indicate the user that you're restoring previous state
restore the stack of your controllers in the navigation controller
remove the splash
it's easier to have all this code in the applicationDidFinishLauching : it's all managed at one point.
Morever, when restoring the old state to your navigation controller, you can avoid using the transitions and use animated:NO instead of YES when pushing your controllers. It will be easier to handle from your perspective and the restore time may be decreased if you remove time needed to achieve transitions.