Apple documentation says that any application using a split view controller should make it as a root view controller. But i am struck at a state where, my login screen should redirect me to a split view controller. Is there a way to achieve this?
I am Using storyboards and new to programming. Kindly help.
One quite common way to solve this issue to change the rootViewController of your applications main UIWindow (which again is a property of your AppDelegate) after a successful login.
So, the initial view controller of your app needs to be your LoginViewController that handles the login. After a successful login, you can do something like this:
- (void)switchToMainInterface
{
// Change the root view controller of the application window to the main storyboard
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UISplitViewController *mainSplitViewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"MainSplitViewController"];
UIWindow *mainApplicationWindow = [[[UIApplication sharedApplication] delegate] window];
mainApplicationWindow.rootViewController = mainSplitViewController;
}
Note that this code is just dummy code to make my suggestion a bit more tangible, it makes the following assumptions:
you have a Storyboard called Main in your application bundle
within this Main Storyboard you have a UISplitViewController with the Storyboard ID MainSplitViewController so that you can instantiate it programmatically
you need to import AppDelegate.h to get access to the root UIWindow
Link your LoginViewController to a UIViewcontroller. In this controller drag an UIContainerView, and embed your UISplitViewController in it.
I created a custom segue class and implemented the following code. I am not sure what it does to my application. It seems a bit high level code to me as I am an amature but it works fine. Hope you find it useful.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Override point for customization after application launch.
// UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
// UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
// splitViewController.delegate = (id)navigationController.topViewController;
return YES;
}
Commented the above code, I believe this is to pause UISplitViewController from loading into UIWindow.
And my custom segue --> segue.m is as follows..
#import "Seague.h"
#implementation Seague
-(void)perform
{
UIViewController *sourceViewController = (UIViewController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *)self.destinationViewController;
UISplitViewController *splitViewController = (UISplitViewController *)destinationViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
UIWindow *window = [UIApplication sharedApplication].keyWindow;
window.rootViewController = destinationViewController;
window.rootViewController = sourceViewController;
[UIView transitionWithView:sourceViewController.view.window duration:0.5 options:UIViewAnimationOptionTransitionNone animations:^{
window.rootViewController = destinationViewController;
} completion:^(BOOL finished){}];
}
#end
This segue is triggered when my login button is pressed and the login details are valid.
My rootViewController is the viewController that has my login button and not UISplitViewController.
Reference: This is not a code I wrote. Found it somewhere on web after searching for 2 days. Will update the source link for reference soon.
Thankyou all for your responses :)
Related
I am begin to study iOS and I try to do left navigation with MMDrawerController
my AppDelegate didFinishLaunchingWithOptions code is:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *leftView = [mainStoryboard instantiateViewControllerWithIdentifier:#"LeftViewController"];
UINavigationController *leftNav = [[UINavigationController alloc]initWithRootViewController:leftView];
UIViewController *centerView = [mainStoryboard instantiateViewControllerWithIdentifier:#"CenterViewController"];
UINavigationController *centerNav = [[UINavigationController alloc]initWithRootViewController:centerView ];
self.drawerController = [[MMDrawerController alloc] initWithCenterViewController:centerNav leftDrawerViewController:leftNav];
self.drawerController.openDrawerGestureModeMask = MMOpenDrawerGestureModePanningCenterView;
self.drawerController.closeDrawerGestureModeMask = MMCloseDrawerGestureModePanningCenterView;
self.window.rootViewController = self.drawerController;
[self.window makeKeyAndVisible];
// Override point for customization after application launch.
return YES;
}
So it's work fine, but I have LoginViewController on my app, and if user has no saved token on NSUserDefaults, I must show LogionViewController.
Of course side menu must be hidden on LoginViewController.
I Tried to switch to LoginViewController inside my CenterViewController:
- (void)viewDidLoad {
[super viewDidLoad];
LoginViewController * vc = [[LoginViewController alloc] init];
AppDelegate *app = [[UIApplication sharedApplication] delegate];
[app.drawerController setCenterViewController:vc withCloseAnimation:YES completion:nil];
}
But I have black screen only.
What I do wrong?
Thanks
What you're doing is a bit weird because you are setting the new centerViewController (of type LoginViewController) within the current one (of type CenterViewController), and once that is done the latter one will be deallocated because there are no more references to it. This might somehow be causing the black screen.
One solution would be to have the LoginViewController outside the MMDrawerController, and always present it at the beginning. If there is no token, then quickly (without animation) present the MMDrawerController and the LoginViewController won't even be seen. This way also allows you to easily dismiss back to the login screen if the user logs out.
Another option is to just present your LoginViewController from the CenterViewController modally (or however you like really) using presentViewController:animated:completion:, and then just dismiss it when they log in.
My app has to have a launch screen where I perform some operations such as updating web content. After the process is done, I display the whole app interface within a UITabBarController. At some point, the app has to go back to this launch view controller to handle the update of the application data.
Apple specifically states that a UITabBarController should be the root view controller of any app.
I'm looking for clever ways of presenting a UIViewController before a UITabBarController without embedding both of them in a UINavigationController.
I currently have the setup I want to avoid (UINavigationController -> UITabBarController) because it works and makes sense. I'm afraid Apple wont like it, so i'm looking forward for some light in the subject.
However, nothing that I've read says that the root controller has to remain the same throughout the life of the app. What about something like...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.tabController = (UITabBarController *)[self.window rootViewController];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.altController = [storyboard instantiateViewControllerWithIdentifier:#"AlternateController"];
return YES;
}
- (void)swapRootControllers {
if ([[self.window rootViewController] isKindOfClass:[UITabBarController class]]) {
self.window.rootViewController = self.altController;
} else {
self.window.rootViewController = self.tabController;
}
}
...assuming all the supporting variable declarations and storyboard implementation.
I'm building an app where the user can select a project from another ViewController and then open a split ViewController. I see that I can't set the other ViewController as initial, but I've come to with a secondary solution.
I set the split view as initial, and navigate to the fake initial ViewController on the viewDidAppear method as if it was initial. Then set the selected project in a global variable and close the project page like this:
[self dismissModalViewControllerAnimated:YES]
To open the splitview.
My question:
Can I use this approach? Will apple approve it if the app works as expected?
Thanks!
Edit:
This code is in my AppDelegate.m, what should it be changed to?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UISplitViewController *splitviewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitviewController.viewControllers lastObject];
splitviewController.delegate = (id)navigationController.topViewController;
return YES;
}
Edit 2:
// Override point for customization after application launch.
UISplitViewController *splitviewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitviewController.viewControllers lastObject];
splitviewController.delegate = (id)navigationController.topViewController;
How do I reference the self.window.rootViewController to the SplitViewController? So that it doesn't think this viewcontroller will "host" the splitview?
Edit 3:
// Close the ProjectsViewController and open the selected project
[self presentViewController:_detailViewController animated:YES completion:^{
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:_detailViewController];
}];
Edit 4:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present a Split View Controllers modally
method is linked with button:
-(void)selectedProject {
// The user opened a project
// Override point for customization after application launch.
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil]; // assuming your split view controller in storyboard with name "Main" in project's main bundle
UIViewController *splitViewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"splitVC"];
UISplitViewController *splitviewController = (UISplitViewController *)splitViewController;
UINavigationController *navigationController = [splitviewController.viewControllers lastObject];
splitviewController.delegate = (id)navigationController.topViewController;
// Close the ProjectsViewController and open the selected project
[self presentViewController:splitViewController animated:YES completion:^{
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:splitViewController];
}];
}
Showing the projectsPage:
- (IBAction)closeProject:(id)sender {
// Show the selectSubjectBanner
_selectSubjectBanner.hidden = NO;
// Save the current subject in case the user edited it
[self saveCurrentSubject];
// Present the ProjectsViewController
[self performSegueWithIdentifier:#"projectsSegue" sender:nil];
}
}
closing the projectspage:
- (void)selectedProject {
// The user opened a project
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil]; // assuming your split view controller in storyboard with name "Main" in project's main bundle
UISplitViewController *splitViewController = (UISplitViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"splitVC"];
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
// Close the ProjectsViewController and open the selected project
[UIView transitionFromView:[[[[[UIApplication sharedApplication] delegate] window] rootViewController] view]
toView:splitViewController.view
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:^(BOOL finished) {
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:splitViewController];
}];
}
better to set window's rootViewController
[[[[UIApplication sharedApplication] delegate] window]
setRootViewController:yourViewController];
Edit: Here is code how to instantiate view controller from storyboard,
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil]; // assuming your split view controller in storyboard with name "Main" in project's main bundle
UIViewController *splitViewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"splitVC"];
make sure you set Storyboard ID for your view controller in storyboard, Identity Inspector tab, splitVC(for example)
Edit: So, UISplitViewController is a container view controller itself, thus we can't hold it in UINavigationController, UITabBarController, add as childViewController, .. etc. And also we can't present it modally, thereby we don't get animated presentation. UISplitViewController should be the window's rootViewController. Simplest way to get animated transition between rootViewController and splitViewController is to use UIView's transitionFromView class method and visually swap their views, along changing rootViewController property. Here is your code edited.
- (void)selectedProject {
// The user opened a project
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil]; // assuming your split view controller in storyboard with name "Main" in project's main bundle
UISplitViewController *splitViewController = (UISplitViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"splitVC"];
UINavigationController *navigationController = [splitviewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
// Close the ProjectsViewController and open the selected project
[UIView transitionFromView:[[[[[UIApplication sharedApplication] delegate] window] rootViewController] view]
toView:splitViewController.view
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:^(BOOL finished) {
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:splitViewController];
}];
}
You can set any viewcontroller as your inital viewcontroller
You cannot launch a viewcontroller in viewDidAppear method. Thats bad.
Apple will not be rejecting app when considering the app transitions. It up to the developer.
You can set any viewcontroller on top by resetting the rootviewcontroller of window
Instead of storyboard check your logic and launch the necessary viewcontroller from appdelegate. Replace the window rootViewController respectively as mentioned by Seryozha
My app consists of a navigation controller and view controllers. Some parts of my UI is done on storyboard and some are initiated from code (in viewDidLoad). My goal is to load childViewController X (consists of a back button, nag bar, label and table) when the app is launched from a push notification "properly" through this method in appDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
This might be a simple question to you guys but I've asked several questions the pass few days (you can look at my questions history). After trying several methods, my app either:
crashes
loads without navigation bar
loads with navigation bar but no label (back button does not work)
loads with navigation bar but with black screen underneath
Only the tableView is dragged onto storyboard. The navigation bar is inferred but I have code that dictates its back button and title. The rest is all done through code in the .m file.
I know people have asked this question before but none of the methods worked. How can I load this childViewController properly through a push notification?
EDIT/UPDATE:
My code so far:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (dictionary != nil)
{
UIStoryboard *mainstoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController* firstVC = [mainstoryboard instantiateViewControllerWithIdentifier:#"NotificationsViewController"];
[self.window.rootViewController addChildViewController:firstVC];
[(UINavigationController *)self.window.rootViewController pushViewController:firstVC animated:NO];
self.window.rootViewController.childViewControllers);
}}
Error Code:
'-[SWRevealViewController pushViewController:animated:]: unrecognized selector sent to instance 0x14575f20'
SWRevealViewController is my library for my sidebar menu view controller.
UPDATE2:
I've also tried this method:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:#"NotificationsViewController"];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = initViewController;
[self.window makeKeyAndVisible];
This loads the correct viewController but without a navigation bar.
Try this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(dictionary != nil) {
UIStoryboard *mainstoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *firstVC = [mainstoryboard instantiateViewControllerWithIdentifier:#"NotificationsViewController"];
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
// Navigation already created
if(navigationController) {
[navigationController pushViewController:firstVC animated:NO];
// Navigation not created
} else {
UINavigationViewController *navigationController = [[UINavigationViewController alloc] initWithRootController:firstVC];
[self.window setRootViewController:firstVC];
[self.window makeKeyAndVisible];
}
}
return YES;
}
You had some problem in your code:
You need to have a UINavigationController in order to have a navigation bar and manage the push/pop actions, so your window root controller should be a UINavigationController.
If you need to initialize a UINavigationController the best and simple way is to initialize it with a root controller directly, so leave the push/pop for user actions: an application has always a root controller, so why don't you create it immediately?
The "addChildController" is another thing: it is used to create custom container controller, it means that you have to implement all the business logic manually. I suggest you to use it only if you are experienced enough, since it could be difficult - Cocoa has enough components to leave it for a small set of applications.
You are using a third part controller, the "SWRevealViewController". They should have an example project, you can try to "copy" it and them customize it for your own purpose.
Let me know if your code works well, otherwise post the new error and I'll try to help you
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)