I have a view controller subclass, SignInViewController, used for sign in that might be needed at any time. Rather than have every view controller in my app listen for the notification that sign in is needed, I'd rather have the app delegate do it.
But how do I trigger it from my app delegate?
Do I put the SignInViewController in my main storyboard? If so, how do I access my storyboard from my app delegate? Or is some other approach better?
You can always reference to your app delegate through the UIApplication singleton.
From there you can always get your root view controller.
With your root view controller you can get a reference to the storyboard.
Once you have your story board all you do is instantiate an instance of the view controller you want.
Present it.
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
MainViewController *mvc = (MainViewController *)appDelegate.window.rootViewController;
LoginViewController *lvc = [mvc.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
[currentVC presentModalViewController:lvc animated:YES];
There may be a more direct way of getting a reference to your storyboard but this will almost always get it for you.
To show a view controller from anywhere (including the app delegate), I have used this code with success in iOS 8+ (I'm not sure about earlier compatibility). It will present from the modal view if there is one.
YOURAppDelegate *appDelegate = (YOURAppDelegate *)[[UIApplication sharedApplication] delegate];
UINavigationController *rootNavC = (UINavigationController *)appDelegate.window.rootViewController;
UIViewController *topVC = rootNavC.topViewController;
UIViewController *myNewVC = [rootNavC.storyboard instantiateViewControllerWithIdentifier:<YOUR STORYBOARD ID>];
if (topVC.presentedViewController)
{
if ([topVC.presentedViewController class] == [UINavigationController class])
{
dispatch_async(dispatch_get_main_queue(), ^{
[((UINavigationController*)topVC.presentedViewController) pushViewController:myNewVC
animated:YES];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[topVC.presentedViewController.navigationController pushViewController:myNewVC
animated:animated];
});
}
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[rootNavC pushViewController:myNewVC
animated:animated];
});
}
I usually wrap this in a convenience method and pass in my instantiated view controller. To OP - you would create SignInViewController in the storyboard, assign it a unique storyboard ID, and then substitute that ID in the above code.
Related
I wonder why my managedObjectContext is nil (in my TableViewController class) when i pass it like that in app delegate:
// Fetch Main Storyboard
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
// Instantiate Root Navigation Controller
UINavigationController *rootNavigationController = (UINavigationController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"rootNavigationController"];
// Configure View Controller
TableViewController *viewController = (TableViewController *)[rootNavigationController topViewController];
if ([viewController isKindOfClass:[TableViewController class]]) {
[viewController setManagedObjectContext:self.managedObjectContext];
NSLog(#"Saved");
}
And in console i could see "Saved" output, but when i log managedObjectContext in my class its nil..
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = app.managedObjectContext;
Did solve problem, but i still want to know why previous solution not work?
The reason is that very likely the view controller you are creating in code is actually overwritten with the one sent directly by the storyboard. There is no good reason to create a navigation and view controller from storyboard like this in the app delegate.
So your solution is the correct one and also quite common to have the view controller get its context from a singleton or the app delegate. Keep this pattern and stop worrying about the other problem.
I have some logic that requires a different view to appear once a button is pressed. This code bellow works from the appDelegate but not in my ViewController implementation. Is there a way to change the view controller storyboard from the current ViewController implementation?
self.window.rootViewController =
(UIViewController *)[[UIStoryboard storyboardWithName:#"Main" bundle: nil] instantiateViewControllerWithIdentifier:#"homeView"];
Along as the controller's view is currently on screen, it has a non-nil window property that points to its window. Also, if the controller with this code was made in the same storyboard, you can just use self.storyboard instead of getting a storyboard from its name.
self.view.window.rootViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"homeView"];
You can get the window from the UIApplication instance:
UIApplication *application = [UIApplication sharedApplication];
UIViewController *newRoot = [self.storyboard instantiateViewControllerWithIdentifier:#"homeView"];
[(AppDelegate *)application.delegate window].rootViewController = newRoot;
I am using a storyboard in Xcode 5, which appears as so:
My requirement is to push a ViewController (VIEW1 or VIEW2) into view from the app delegate. Essentially it should not matter what view is presently on the screen -- I would just like to make a ViewController appear when the app delegate picks up an external event.
In order to try and achieve this, I have property references to both the TabBarCtrl-Products and NavCtrl-ProductA in my app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_tabBarProducts = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidTabBarProducts"];
NSArray *tabvcs = _tabBarProducts.viewControllers;
for (id controller in tabvcs){
if ([controller isKindOfClass:[VCNavControl_ProductA class]]) {
_navControllerProductA = controller;
break;
}
}
return YES;
}
The app delegate method to push VIEW2 is:
-(void)showVCVIEW2
{
VC_V2 *targetvc = nil;
targetvc = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidView2"];
[[AppDelegate sharedInstance].navControllerProductA pushViewController:targetvc animated:NO];
}
This works OK when VIEW1 is showing at the time showVCVIEW2 is called, however it does not work when ViewCtrl-ProductB is showing. I can see that the new instance of targetvc has been added to the AppDelegate _navControllerProductA's stack, however it does not display.
(Regarding the setting of the the app delegate's rootViewController, I set this to _tabBarProducts after the VC-Splash and VC-Setup ViewCtrls have finished).
I would appreciate very much if anyone can give me an idea on how to achieve this. I suspect my problems stem from having a NavCtrl in a TabBarCtrl, but I do not know a way around this.
Your problem is that the navigation controller you are pushing on is not in the view hierarchy.
You instead could try setting the tabBarController's selected index like this:
[self.tabBarController setSelectedIndex:1];
I have a main view controller and am presenting a child view. For one of the settings inside the child, I need to call the main view controller again. It would just make things a lot easier.
The following causes a hierarchy problem:
#define appDelegate (MyAppDelegate *)[[UIApplication sharedApplication] delegate]
[self presentViewController:[appDelegate mainViewController] animated:YES completion:^{}];
Is there any way to call the mainViewController easily while not losing the state of the child view?
EDIT
Here is how I'm presenting the child view.
ChildViewController *childViewController = [[ChildViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:childViewController];
[childViewController release];
[self presentModalViewController:navigationController animated:NO];
[navigationController release];
You can't present a view controller that has already been presented or is in a navigation stack. What is the mainViewController? Did you instantiate it? Has it been added to the screen yet?
If yes to both, you either need to back into it (dismiss to it) or remove it from it's parent first and then present it.
In your child view, you should have access to your application delegate and its mainViewController property, like this:
MyApplicationDelegate *appDelegate = (MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
I will try to explain this as best as I can.
My application has a TabBarController which functions as the main navigation
I have a modal view that I segue to to add a list. that screen can be reached from 2 different viewcontrollers.
From the main route I simple just close the modal and all is fine. However from the second route I need to be able to open up an entirely new ViewController.
The issue that I am having is that I can not seem to open that ViewController with the TabBar and NavBar included.
This is the code I am currently playing with to try to get it to work.
UITabBarController *tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainInterface"];
tabController.selectedIndex = 1;
//_window.rootViewController = tabController;
UINavigationController *groceryNavController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainNavController"];
UIViewController *groceryViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"GroceryViewController"];
UIViewController *currentVC = self;
[currentVC.navigationController pushViewController:groceryViewController animated:YES];
One way to to do it is through the delegate. If in the delegate, the relevant navigation controller is called:
self.navigationController
Then you would have to do:
YourAppDelegate *delegate = (YourAppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:groceryViewController animated:YES];
(replace "YourAppDelegate" with the actual name of your app delegate)