I am trying to incorporate State Restoration in my app. I have it working fine for the most part, but presenting a navigation controller for a modal view on top of another navigation controller seems challenging.
For testing, I created a new split-view app on the iPad, with navigation controllers for both sides of the split view, and a Master and Detail view controller for each side, the roots of their respective navcontrollers. In the master view, you can click on a button to push a new TestViewController onto the navController stack programatically. I hook up the splitView in the storyboard, add restorationIDs to everything, opt-in to the delegate, provide a restoration class and adhere to the UIViewControllerRestoration protocol for TestViewController (since it's created programmatically) and everything works fine. If I close the app and retort it, it will start the TestViewController pushed onto the master's navcontroller. So far so good.
I then change the button handler to present the TestViewController inside a new UINavigationController, present it onto the master's navigation controller, to show a modal view (instead of pushing it on the nav stack). Now, when I relaunch the app, there is no modal view there anymore. TestModalViewController's viewControllerWithRestorationIdentifierPath:coder: is actually called correctly as before, but the modal view is never presented for some reason.
Here is the code for what I'm talking about
MasterViewController.h:
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
modal.modalPresentationStyle = UIModalPresentationFormSheet;
modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:modal animated:YES completion:nil];
return;
}
TestModalViewController.m:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationClass = [TestModalViewController class];
test.restorationIdentifier = [identifierComponents lastObject];
return test;
}
Perhaps the UINavigationController that is created to display modally is never preserved? Not sure why, because it does have a restorationIdentifier.
Edit:
After further testing, it turns out if I remove the UINavigationController from the the pushButton: code, and present the TestModalViewController instance directly, it gets restored correctly. So something about the UINavigationController being presented from another UINavigationController?
This works (though not what I really want):
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
//UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
//modal.modalPresentationStyle = UIModalPresentationFormSheet;
//modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:test animated:YES completion:nil];
return;
}
EDIT:
Attached link to test project: dropbox.com/sh/w8herpy2djjl1kw/vw_ZWqimgt
It's basically the Core Data master-detail template; run it on the iPad simulator. The + button in Master invokes the TestModalVC; if you then press the Home button, then kill debugger and launch again, you see the snapshot contains the TestModalVC but when the app is launched, it doesn't get restored
You can either create your own restoration class to handle this, or add the following to your app delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSString *lastIdentifier = [identifierComponents lastObject];
if ([lastIdentifier isEqualToString:#"ModalTestID"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"ModalTestID";
return nc;
}
else if(...) //Other navigation controllers
{
}
return nil;
}
More information in the documentation.
Related
I'm new to Objective-C and I want to add a UINavigationBar on my CatrgoryBIDViewController. I have proceed UIButton in InstructionBIDViewController.m file that should navigate to CatrgoryBIDViewController. Here is the function code:
- (IBAction)proceed:(id)sender {
viewControllercat =
[[CatrgoryBIDViewController alloc]
initWithNibName:#"CatrgoryBIDViewController"
bundle:nil];
UINavigationController *nav =
[[UINavigationController alloc]
initWithRootViewController:self.viewControllercat];
//[self.navigationController pushViewController:viewControllercat animated:YES];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
But it is not setting the UINavigationBar.
You should read the documentation here to understand the way a NavigationController is working. For your case:
If your current ViewController (where your proceed-method is implemented) has a NavigationController (is child of it), you can push another ViewController onto the stack of that NavigationController with
[self.navigationController pushViewController:viewControllercat animated:YES];
In this case you do not need to initialize another NavigationController, but in your CatrgoryBIDViewController in viewWillAppear you need to make the NavigationBar visible if it was not before already with
[self.navigationController setNavigationBarHidden:NO animated:YES];
If your current ViewController does not have a NavigationController, you can not push another ViewController on top of it and can not show the NavigationBar of it (although you can create your own NavigationBar and add it to the View of the ViewController, but without the usual Navigation-behaviour embedded).
If you open your ViewController programmatically (e. g. from the AppDelegate) you are correct to do so by your call:
viewControllercat = [[CatrgoryBIDViewController alloc] initWithNibName:#"CatrgoryBIDViewController" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc] initWithRootViewController:self.viewControllercat];
My apologies - After having reread your question, I am going to tweak my answer some.
-(IBAction)proceed:(id)sender
{
viewControllercat = [[CatrgoryBIDViewController alloc] init];
UINavigationController * nc =
[[UINavigationController alloc] initWithRootViewController:vc];
// We now need to display your detail view but cannot push it.
// So display modally
[self presentViewController:nc animated:YES completion:Nil];
}
The above should result in your DetailViewController - CategoryBIDViewController being displayed on top of your InstructionBIDViewController and it should have a UINavigationController in it.
I had an application in which I need to have a login or register system before the tab bar controller is added to the window, such as in Instagram.
I have added 5 navigation controllers (with view controller as its root) to the UITabBarController and then set it as the root of the window. Before that, I need to have another UINavigationController for the login system.
If I add that, how do I remove it before adding tab?
Another problem is that I also have to handle logging out, so I need to come back to it.
Can anybody help me with me how to do this?
I suggest to you loading Loginview From Delegate window as we did Normally. and from Logged Success its button click you set TabbarController like this:-
UIViewController *viewControllerPostalCode2 = [[cntrServices alloc] initWithNibName:#"cntrServices" bundle:nil];
UIViewController *viewControllerPostalCode3 = [[cntrInquiryViewController alloc] initWithNibName:#"cntrInquiryViewController" bundle:nil];
UINavigationController *navPostage1 = [[UINavigationController alloc] initWithRootViewController:viewControllerPostalCode2];
UINavigationController *navPostage2 = [[UINavigationController alloc] initWithRootViewController:viewControllerPostalCode3];
//
navPostage1.navigationBar.tintColor =DARK_BACKGROUNDNavigation;
navPostage2.navigationBar.tintColor =DARK_BACKGROUNDNavigation;
//
self.tabBarForServicesController = [[UITabBarController alloc] init];
self.tabBarForServicesController.delegate=self;
self.tabBarForServicesController.viewControllers = [NSArray arrayWithObjects:navPostage1,navPostage2,nil];
[self.navigationController pushViewController:self.tabBarForServicesController animated:YES];
I Done this type of Task using this method and at the Logged Out just poptoRootviewController work back to the Logged in Screen.
I have done this like adding my tabbarcontroller as rootViewController to UIWindow and in applicationbecomeactive delegate i present a controller with navigation controller like when required and simply dismiss the controller when authentication done
UIViewController *topViewController = [self.navController topViewController];
if (![topViewController isKindOfClass:[LGLoginViewController class]]) {
[self.navController popToRootViewControllerAnimated:YES];
self.navController = nil;
LGLoginViewController* loginView = [[LGLoginViewController alloc] initWithNibName:#"LGLoginViewController"bundle:nil];
if (!self.navController) {
self.navController = [[UINavigationController alloc] initWithRootViewController:loginView];
} else {
[self.navController initWithRootViewController:loginView];
}
self.navController.delegate = self;
[self.window.rootViewController presentModalViewController:self.navController animated:NO];
}
to begin with, here is some code:
- (void)viewDidLoad
{
[super viewDidLoad];
FirstViewController *first = [[FirstViewController alloc] init];
SecondViewController *second = [[SecondViewController alloc] init];
MBPullDownController *pullDownController = [[MBPullDownController alloc] initWithFrontController:first backController:second];
[self.navigationController addChildViewController:pullDownController];
}
- (void)pushAnotherViewController:(NSNotification *)notification
{
AnotherViewController *another = [self.storyboard instantiateViewControllerWithIdentifier:#"anotherViewController"];
[self pushScheduleViewController:another];
}
I use the MBPullDownController open source control. Using to seperate view controllers I load into the pull down controller. This code is in a view controller called RootViewController which is embedded in a UINavigationController. Then there's a method for pushing another view controller in the navigation controller. It's when I try to use the method (in AnotherViewController) popToRootViewController: that my app crashes and the EXC_BAD_ACCESS message comes up in the console.
EDIT
This is my code in "AnotherViewController"
- (void)popBack
{
RootScheduleViewController *root = [[RootScheduleViewController alloc] init];
[self.navigationController popToViewController:root animated:YES];
}
You are getting a bad access error when you call popBack because you are creating a new instance of the view controller and then trying to pop to it. For a navigation controller, the view controller must be part of the navigation stack in order to pop to it. So if an instance of this view controller exists, find it in the navigation stack and pop to it.
for(UIViewController * viewController in self.navigationController.viewControllers){
if([viewController isKindOfClass:[RootScheduleViewController class]]){
[self.navigationController popToViewController:viewController animated:NO];
break;
}
}
I am relatively new to iOS, hence I apologize for any inconsistency in my question. I need help with the following issue with an app I'm trying to build. My issue is this: The app i am working has a navigation based functionality with a tableview(daily filled by user) and a detailed tableview listing the inputs of the user, but this is just one functionality of the app.
I want to have a main tab based view where one of the tabs(each tab representing a functionality) points to this module.
I wanted to ask for steps and changes i need to make to for example app delegate or rootviewcontroller(I can post the code if it helps better) to make is so that the app starts with a mutli-tabbed bar view where one tab refers to view linked to the rootviewontroller of the navigation-based app.
For summary: Need a main tab bar view where one tab points to the rootviewcontroller highlighted in the screenshot(link below)
If helpful here is a relevant function code i have in app delegate :
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
RootViewController *rootViewController = (RootViewController *)[[navigationController viewControllers] objectAtIndex:0];
rootViewController.managedObjectContext = self.managedObjectContext;
//Next TWO LINES FOR COLOR BACKGROUND
[[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundColor:[UIColor redColor]];
}
PS:Here is the screenshot for the storyboard: where i would like to have one tab refer to the view(highlighted in the screenshot) which is linked class rootviewcontroller.m/h
The screenshot: http://i.stack.imgur.com/G9AXI.png
edit: The actual question can be seen as: How and what do i need to do to have a tabbarviewcontroller which i would add with storyboard become my rootviewcontroller instead of the navigationcontroller(highlighted in black in the screenshot: http://i.stack.imgur.com/G9AXI.png).
My current rootviewcontroller.m manages anything related to the tableview of the current navigationviewcontroller, do i need to change that also?.
I apologize for excessiv details, I am really new to iOS dev.
From this one http://i.stack.imgur.com/suLBm.png I tried to embedd in tab barviewcontrol only with storyboard to this one http://i.stack.imgur.com/TZxLo.png I tried to embedd in a tab controller just by story but i get an error :'NSInvalidArgumentException', reason: '-[UIViewController setManagedObjectContext:]: unrecognized selector sent to instance 0x8184e30'
classes related to this are(especially rootviewcontroller.m which is a navigationcontroller for now:
AppDelegate.{h,m}
Configures the Core Data stack and the first view controllers.
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
RootViewController *rootViewController = (RootViewController *)[[navigationController viewControllers] objectAtIndex:0];
rootViewController.managedObjectContext = self.managedObjectContext;
}
RootViewController.{h,m}
Manages a table view for listing all values entered. Provides controls for adding and removing these values.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = self.editButtonItem;
}
DetailViewController.{h,m}
Manages a detail display for display details of each entered value.
My initial guess is that i need to change the rootviewcontroller appdidfinishlaunching.
Any suggestions ?
In fact now you have:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
UITabBarController *tabController = (UITabBarController *)self.window.rootViewController;
RootViewController *rootViewController = (RootViewController *)[[[[tabController viewControllers] objectAtIndex:0] viewControllers] objectAtIndex:0];
rootViewController.managedObjectContext = self.managedObjectContext;
}
So you actually need a UITabBarViewController in the Storyboard and you can point to the UINavigationController if you want the ability to push other controllers.
You don't need other UINavigationControllers as I saw in your screenshot, as long as the rootviewcontroller is an UINavigationController.
You can add the UINavigationController as first of the tabs and then you can go and fill the other tabs with the viewcontrollers that you need displayed.
SO basically you need to create UITabBarController as rootviewcontroller.
Let me know if I understood your question correctly.
Here is an example of UITabBarController :
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
//Here you set your controller
UIViewController* centerController = [[UIViewController alloc]init];
UINavigationController *navCenter = [[[UINavigationController alloc] initWithRootViewController:centerController] autorelease];
UITabBarController *tabBarController = [[[UITabBarController alloc] init] autorelease];
tabBarController.viewControllers = [NSArray arrayWithObjects:navCenter,nil];
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.rootViewController = tabBarController;
return YES;
}
Let me know if it worked.
You should have something like this :
I'm experimenting with OAuth 2.0. which brings up a window containing a UIWebView as a canvas for the authentication server to communicate through. Right now, it is being shown as a modal view with its own view controller, and doesn't have a back button or cancel button. So the user has no way to escape from the sign-in process.
I want to have the webView handled by a navigation controller so I can push the webView's view controller.
I am having problems with doing this. It seems to me I should be able to just create a UINavigationController object with the root view controller being the main view controller, like this
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self];
However, this line crashes the program without any error message in the debugger.
I thought I could place a line to push the web view controller immediately after the line above, like this:
[navController pushViewController:windowController animated:YES];
But, without getting past the first line, I can't begin to work out the details on getting the web view to show and then configure the back button.
UPDATE
Here is some context. This is in a single view application. The following code is in the main view controller, which brings up the sign-in dialog. I would like to replace the last line, where the presentModalViewController is called, with a push of the windowController onto a navController stack. Note the commented code at the end, where the initialization of the navigation controller is located.
- (IBAction)signInClicked:(id)sender {
if (![self isSignedIn]) {
// Sign in
[self runSigninThenInvokeSelector:#selector(updateUI)];
}
[self updateUI];
}
- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
NSString *clientID = mClientId;
NSString *clientSecret = mClientSecret;
// Show the OAuth 2 sign-in controller
NSString *scope = [GDataServiceGoogleBlogger authorizationScope];
GTMOAuth2ViewControllerTouch *windowController;
windowController = [[GTMOAuth2ViewControllerTouch controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)kKeychainItemName
delegate:(id)self
finishedSelector:#selector(windowController:finishedWithAuth:error:)] retain];
//UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self]; // This causes a crash when it is not commented out.
[self presentModalViewController:windowController animated:YES];
}
The application delegate didFinishLaunchingWithOptions is set up this way:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[blogSpotViewController alloc] initWithNibName:#"myNibName" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Well, you can present this as a modal controller. You just need to arrange to call dismissModalViewController: in response to a button or some other event on the 'windowController'.
But if you want to use a navigation controller, then you can set that up in application:didFinishLaunchingWithOptions: like so:
// create window here like now
blogSpotViewController *viewController = [[blogSpotViewController alloc] initWithNibName:#"myNibName" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[viewController release];
// TODO: hold onto this navController in a property if you really need that.
self.window.rootViewController = navController;
[navController release];
// present window here as you are now
This embeds your main view controller in a navigation controller. Then back in your runSigninThenInvokeSelector: you can...
[self.navigationController pushViewController:windowController animated:YES];
Hope that points you in the right direction.