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
Related
I have one view controller with out navigation controller. Its name is LoginViewController. In my AppDelegate, I want to keep my LoginViewController as root view controller.
How can I do this in Objective-C? How can I set my view controller as root view controller?
Note: My view controller does not have navigation view controller. It's a single view controller.
do like
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. get the Storyboard Name
UIStoryboard* main = [UIStoryboard storyboardWithName:#"Main"
bundle:[NSBundle mainBundle]];
//2. get the ViewController using Storyboard ID
UIViewController *viewConr = [main instantiateViewControllerWithIdentifier:#"HomeViewController"];
// 3.finally assign the Root
self.window.rootViewController = viewConr;
[self.window makeKeyAndVisible];
return YES;
}
for E.g
Without storyboard:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
LoginViewcontroller *Vc = [[LoginViewcontroller alloc]init];
self.window.rootViewController = Vc;
[self.window makeKeyAndVisible];
return YES;
}
If using storyboard just make that view controller as initial view controller from storyboard.
If you want to set home page VC as root vc from login page VC
Import appdelegate in login page
#import "AppDelegate.h"//Import in Login page VC
self.delegate = (AppDelegate *) [[UIApplication sharedApplication] delegate]; // In viewDidLoad
Write below code where you navigate
//Make root view controller
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomeViewController * hvc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"Home"];//Your Home page story board ID
self.delegate.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:hvc];
[self.delegate.window makeKeyAndVisible];
I need to present a view controller from app delegate.
When a phone notification comes in, I am able to decide which one of 3 view controllers (named ForumViewController, BlogViewController & NewsViewController) should be presented by analyzing the 'userInfo' in the method 'didReceiveRemoteNotification'.
But when i try to present the appropriate view controller using storyboards or the code below:
self.viewController = [[MembersViewController alloc] initWithNibName:#"MembersViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
Then, the app gives the error 'Warning: Attempt to present whose view is not in the window hierarchy!'. Also it gets stuck on a particular view controller.
Please keep in mind that the view controllers that I am trying to present are not part of the flow when the app starts (the flow is LogoViewController -> SplashViewController -> HomeViewController).
The HomeViewController & MembersViewController are essentially the main menu pages for public & private viewing. Here I have to display something to the viewer.
choice-1
using push
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
MembersViewController *vc = [navController.storyboard instantiateViewControllerWithIdentifier:#"MembersViewController"];
[navController pushViewController:vc animated:YES];
using present
MembersViewController *root = (MembersViewController *)self.window.rootViewController;
UIViewController *vc = [root.storyboard instantiateViewControllerWithIdentifier:#"MembersViewController"];
[root presentViewController:vc animated:YES completion:NULL];
upadted
UIStoryboard *mainstoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
MembersViewController* pvc = [mainstoryboard instantiateViewControllerWithIdentifier:#"MembersViewController"];
[self.window.rootViewController presentViewController:pvc animated:YES completion:NULL];
Loading a view controller from the storyboard:
[self performSelector: #selector(ShowModalViewController) withObject: nil afterDelay: 0];
-(void)ShowModalViewController{
NSString * storyboardName = #"MainStoryboard";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER_OF_YOUR_VIEWCONTROLLER"];
[self.window.rootViewController presentViewController:vc animated:YES completion:nil];
}
Identifier of your view controller is either equal to the class name of your view controller, or a Storyboard ID that you can assign in the identity inspector of your storyboard.
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 :)
I want to add a navigation view controller prior to the user getting to the splitview controller. I have tried a few ways of changing the root controller when I want to go from navigation controller to splitview controller but I don't seem to be setting the delegate the right way when I do this.
Code WITHOUT nav view (works perfectly):
AppDelegate
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
UINavigationController *masterNavigationController = splitViewController.viewControllers[0];
MasterViewController *controller = (MasterViewController *)masterNavigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
Code with nav view prior to SplitView
AppDelegate
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController* rootController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:#"dummy"];
self.window.rootViewController = rootController;
[self.window makeKeyAndVisible];
DummyViewController
AppDelegate *appDelegateTemp = [[UIApplication sharedApplication]delegate];
appDelegateTemp.window.rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
This takes me from the DummyViewController that I launched into, to the splitview controller which is the initial view controller in Storyboard. Which is fine however, when I do it this way none of the delegates get called. This is probably because when changing root controllers, it is not setting the delegates properly. How can I get this to work the right way?
It seems the only really non-hacking way to do it is to present a modal view over the split view in the detail view controller
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DummyViewController *dummy = (DummyViewController *)[storyboard instantiateViewControllerWithIdentifier:#"dummy"];
[self presentViewController:dummy animated:NO completion:nil];
By setting animation to NO, the user does not see the split view loaded behind it.
I added a new UIViewController to get a log in issue fixed and now the button in the NavigationBar on the next scene are inactive. Here is the code I used to load the new StoryBoard:
-(void)newSotrybooard{
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"LogedIn" bundle:nil];
UIViewController *initialSettingsVC = [settingsStoryboard instantiateInitialViewController];
initialSettingsVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:initialSettingsVC animated:YES completion:NULL];
}
I added the new storyboard after I couldn't get the NavigationBar to show up and push without and error on the main storyboard. This is the only code that has changed. The Navigation Controller does not have a class associated with it and neither does the TableView to follow. I can also not scroll the Table view. Thank you for your help!
Update:
I updated the code above and
The error I receive is:
Warning: Attempt to present <UINavigationController: 0x109fa0ce0> on <PrivateViewController: 0x109f6ef80> whose view is not in the window hierarchy!
Based on the information you have provided, I surmise that the following could be helpful -
Yes, you have used instantiateViewControllerWithIdentifier.
But, remember it instantiates and returns the view controller with
the specified identifier. You're missing that.
Provide the same name to your new Storyboard in your code and
File Inspector (like you named it LogedIn above) and it
should be set in the Main Interface (Target >> General >> Main
Interface).
See rootViewController? The rootViewController
for the window needs to be assigned (either via Storyboard or
programmatically). Please check. I have added a UINavigationController in my example below -
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"StoryboardName" bundle:nil];
ViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"StoryboardName"];
vc.title = #"Is this title visible?";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nav;
return YES;
}
For the ViewController.m
-(void) goToSettings {
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"SettingsStoryboard" bundle:nil];
UIViewController *initialSettingsVC = [settingsStoryboard instantiateInitialViewController];
initialSettingsVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:initialSettingsVC animated:YES completion:NULL];
}