I have an app where I re-init the app if it's been in the background for too long. When the user opens up the app after the allotted time, the re-init happens, and I display the splash screen while I get the data I need. When I get the data from the server I set the window.rootViewController property to a new value. If the old root view controller has had a view controller presented modally, and that view controller was being displayed when the re-init happens, dealloc doesn't get called on the view controller (I've tested this by putting NSLog's in the dealloc method). In the case where a modal view controller was not presented, the dealloc gets called as expected.
Does anyone know of a solution to this? I'm not sure if it's an Apple bug, or if it's something that I need to handle on my own.
The solution that I came up with was before I set the RootViewController, I call
- (void)_dismissRootViewControllersModalViewsIfAny {
UIViewController *rootViewController = self.window.rootViewController;
if (rootViewController.presentedViewController || rootViewController.presentingViewController)
{
[rootViewController dismissViewControllerAnimated:NO completion:nil];
}
}
This is a method that I created, which makes sure that if there is a modal view controller to dismiss, it will be dismissed.
Your modal view is presented by the rootViewController (it's presentingViewController property is set to the rootViewController), that might be the source of your problem.
You can set your rootViewController to an instance of UINavigationController and then just use its setViewControllers:animated: method to display a freshly instantiated view controller instead of switching the window's rootViewController.
Related
I have created a custom UIWebView to present a login page. I am able to get this to show by adding it as a subview. But I would like to present this modally and programatically without the use of a storyboard!
Put it in an otherwise empty view controller, and then call
[self presentViewController: animated: completion:]
in the view controller you're wanting to present it from. When you're done, you can call
[self dismissViewControllerAnimated: completion:]
to get rid of it again. This message can be sent to either the presenter or the presented view controller, though in either case it ultimately is the presenter which responds.
I perform some data loading tasks from an Ojective§C class and once everything is loaded, I simply wants to display a Viewcontroller subclass prepared in a storyboard.
So when everything is ok, the following method is called:
- (void)loadingNextView
{
CABBndGSite *mySite = [CABBndGSite alloc];
CABBndGSelectLanguageViewController *vc = [[mySite myRootViewController].storyboard instantiateViewControllerWithIdentifier:#"SelectLanguageViewController"];
[[mySite myRootViewController] presentViewController:vc animated:YES completion:nil];
}
So I verified that myRootViewController is not nil. It's a UINavigationController class.
vc is not nil so it found my view in the storyboard.
Anyway, the presentViewcontroller message seems to doing what expected.
Certainly a stupid mistake but my poor iOS programming knowledge lets me in the fog!
I use this code from ViewController subclasses with success and as here I get a valid ViewController pointer, I don't understand why it doesn't work.
I also tried to implement the AppDelegate method explained here How to launch a ViewController from a Non ViewController class? but I get a nil navigation pointer. Maybe something not well connected in my application
May I have some explanation?
Kind regards,
UINavigationController maintains a stack of view controllers. You can access this stack through the viewControllers property. To present your view controller, you can:
(a) have the navigation controller push the new view controller on to
the stack (pushViewController:animated:);
(b) have the top view controller in the view controller stack present
the new view controller modally (presentViewController:animated:completion:), or;
(c) add the new view controller to the view controller stack array
manually by assigning a new viewControllers array to the navigation
controller's viewControllers property (setViewControllers:).
I am writing a turn-based game for the iOS platform. The client communicates with a remote server using the CocoaAsyncSocket API. Right now I work on this case: the client has been inactive for a while, and has been disconnected from the server due to timeout. If that's the case, I wish to pop back to the login view when the app enters the foreground, to let the user log back in again.
I assume I have to do this kind of work in the -(void)applicationWillEnterForeground of my app's delegate. Checking whether I'm connected or not is not a problem, but I don't know how to dismiss every presenting view controller to take me back to the root view (which happens to be the login view).
My view controllers are presented modally, but I also have two navigation controllers with table views including push segues.
Any help with this problem is highly appreciated. Thanks in advance!
The jarring way to do it is just replace the rootViewController with a new login view controller.
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if ([self isDisconnected]) {
self.window.rootViewController = [MyLoginController new];
}
}
For storyboards, assuming your initial storyboard is the login storyboard.
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if ([self isDisconnected]) {
UIStoryboard *storyboard = self.window.rootViewController.storyboard
self.window.rootViewController = [storyboard instantiateInitialViewController];
}
}
Depending on your UX, this may or may not be appropriate.
If all of your content views are presented modally from the root view controller (which doesn't sound like a great idea) then from the app delegate (indeed in applicationWillEnterForeground),
// if we need to login
UIViewController *rootViewController = self.window.rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
If the root view controller isn't presenting modally then you need to dismiss in the appropriate way. If any other 'child' view controller is presenting modally then it should dismiss. I guess dismissing when that view controller changes parent view controller will work. You may need to add a notification to inform all view controllers that everything is being torn down.
In the past my app has had only 1 main view controller (MainViewController) and a login view controller (LoginViewController) but now I am moving to a Tab Bar Controller.
Before I was able to do a simple check viewDidLoad of MainViewController for the existence of a username and password in the key chain. If a username and password was not present I used a segue to pop up a modal login view controller.
With the new setup of using a Tab Bar Controller I still only have 1 view controller (MainViewController) which is the root view controller (as of now) and I am trying to do the same thing where it pops up modal of the login screen.
Now when I call the segue in the viewDidLoad of MainViewController:
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
I am getting this error:
Warning: Attempt to present <LoginViewController: 0x1757cd80> on <UITabBarController: 0x17571e50> whose view is not in the window hierarchy!
But if I associate a button to a method that loads the LoginViewController by way of a segue it works fine. I am doing that in the MainViewController like this:
-(void)loadLogin
{
[self performSegueWithIdentifier:#"loadLoginView" sender:nil];
}
I can see from the error message that when I try to perform the segue from the viewDidLoad of MainViewController it's trying to load the LoginViewController from UITabBarController.
Why can I not load the LoginViewController from the viewDidLoad of MainViewController?
Any help with this would be great.
Thanks!
It looks like -viewDidLoad is getting called before your view controller stack is added to the window. Two things you could try are:
Delaying until the next time through the run loop (this should give the view controllers time to get in place) [self performSelector:#selector(loadLogin) withObject:self afterDelay:0];. This method won't allow you to call a method with two arguments directly
You could use -presentViewController: animated: completion:. This will cause your login controller to slide in from the bottom.
I have a root view controller (RVC) that opens up a Modal ViewController (MVC). I then navigate within the MVC to few more VC's via a push. What is the best practice to get from one of those VC's back to the RVC?
Normally I have a delegate from the Modal VC that calls up to the RVC which then dismisses the modal, but if you navigate away from it, but I'm not sure how I would do that if you navigate away from it.
Without seeing any code it is a bit hard to help but let me shoot in the dark here.
I will assume that the first controller presented inside the modal view provides the protocol/delegate to call the dismiss action.
If you use UINavigationController inside your modal view to push other view controllers on the stack you can always obtain the first controller like this
UIViewController * yourFirstController = [[[self navigationController] viewControllers] objectAtIndex:0];
// and then use your delegate to call your dismiss method
// you will need to typecast your controller based on your subclass otherwise will get warning here
if ([[yourFirstController delegate] respondsToSelector:#selector(yourCloseProtocolMethod)]) {
[[yourFirstController delegate] yourCloseProtocolMethod];
}
Don't forget that a delegate doesn't have to be a property of a UIViewController inside your model navigation stack. Consider creating a singleton class that holds a reference to the rootviewcontroller as a delegate. That way any class in your application has access to it and you aren't forced to continually pass it through to every UIViewController that requires it.