I had a tabBarApplication that I have as a template which has a UINavigation template on each tab.
I want to use that sample (sort of) and convert it into a single UIViewController in a nother application. I have presented 2 pieces of code, the first one is my template and the latter is what I am trying to make. Could anyone please give me some hints or help with how to that right? I keep getting errors, but they do not make sense to me as the tabBarApp does not need it a viewController declared.
First Code (the example):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupFetchedResultsController];
if (![[self.fetchedResultsController fetchedObjects] count] > 0 ) {
NSLog(#"!!!!! ~~> There's nothing in the database so defaults will be inserted");
[self importCoreDataDefaultRoles];
}
else {
NSLog(#"There's stuff in the database so skipping the import of default data");
}
// The Tab Bar
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
// The Two Navigation Controllers attached to the Tab Bar (At Tab Bar Indexes 0 and 1)
UINavigationController *personsTVCnav = [[tabBarController viewControllers] objectAtIndex:0];
UINavigationController *rolesTVCnav = [[tabBarController viewControllers] objectAtIndex:1];
return YES;
}
Second Code (what I am trying to make):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupFetchedResultsController];
if (![[self.fetchedResultsController fetchedObjects] count] > 0 ) {
NSLog(#"!!!!! ~~> There's nothing in the database so defaults will be inserted");
[self importCoreDataDefaultRoles];
}
else {
UIViewController *mainViewController = (UIViewController *)self.window.rootViewController;
UINavigationController *readingsTVCnav = [[mainViewController viewController] objectAtIndex:0];
// Override point for customization after application launch.
return YES;
}
The fetching parts of the code are related to Core Data which already set up and working.
The reason for this change is that I want to have a plain view controller set up as the initial screen rather than the tabBarConfiguration.
Cheers Jeff
EDIT: I have added an image for clarity
From what you describe and depict in your image, you have the navigation controller set up as your app's root view controller. So you can access it (if you need to) in your app delegate's didFinishLaunchingWithOptions: method as follows:
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
Although for your app I don't think there's any reason you need to reference the nav controller from your app delegate. All the setup you need (with the exception of your code data code, which you say is already working) can be handled through the storyboard.
In your nagivationController's root viewController (the one with all the buttons) you should set up a segue from each button to the approperiate viewController in your storyboard. Bure sure to set then up as "push" segues so that it will push the view controller onto your navigationController's navigation stack. If there is any particular setup you need to do for the child view controller when the segue happens, you can implement the prepareForSegue: method like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMyViewController"]) {
MyViewController *vc = (MyViewController*)segue.destinationViewController;
vc.title = #"My Title";
vc.someProperty = #"Some Value";
}
}
You can (and should) identify each of your segues in the storyboard with a unique identifier so that you can identify them when this method is called.
Related
I know this question has been asked many times. But I am a newbie and I was not able to find any solutions to help me in my condition. Here is a brief explanation:
I have an app with a UITabBarController as root.
Inside the app delegate I check if user is already logged in. If yes I will open root controller.
self.window.rootViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
My application is working fine. But now I need to implement notification. When I get notification content inside didFinishLaunchingWithOptions:
NSDictionary *notificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
NSString *messageType = [notificationPayload objectForKey:#"messageType"];
Now if there is a message:
if (messageType.length > 0 )
{
//Here based on message I need to open different tabs of TabBarViewController
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
//RootTabBarViewController *listingVC = [[RootTabBarViewController alloc] init];
[(UINavigationController *)self.window.rootViewController pushViewController:tabBarController animated:YES];
}
This piece of code above did not work for me.
And also I don't know how to open different tabs and give value to their badges form here. It always navigate to the first index tab with these code. I have seen other answers say:
self.tabBarController.selectedIndex = 2;
But did not work for me. I have implemented an UITabBarController class and I can set value for the each tab item badges from there but I get my notificationPayLoad in AppDelegate.
As you told that you are using TabBarController with storyboard. Then why are you initialising again? You can just do as following
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if([[NSUserDefaults standardUserDefaults]boolForKey:#"Selected"])
{
[[NSUserDefaults standardUserDefaults]setBool:NO forKey:#"Selected"];
UITabBarController *tabController = (UITabBarController*)self.window.rootViewController;
tabController.selectedIndex=1;
}else{
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:#"Selected"];
UITabBarController *tabController = (UITabBarController*)self.window.rootViewController;
tabController.selectedIndex=0;
}
return YES;
}
Here I am just showing a demo code for accessing TabbarController at different selectedIndex within didFinishLaunching.
Assuming that you have designed(drag n dropped UITabBar/ UITabBarController) in storyboard.
if (messageType.length > 0 )
{
//Here based on message I need to open different tabs of TabBarViewController
NSUInteger indexOfRequiredTab = 2;// pass the index of controller here
id rootObject = self.window.rootViewController;
if ([rootObject isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabBarControllerObject = (UITabBarController *)rootObject;
if (indexOfRequiredTab != NSNotFound && indexOfRequiredTab < tabBarControllerObject.tabBar.items.count) {
tabBarControllerObject.tabBar.items[indexOfRequiredTab].badgeValue = #"2"; //to set badge value
tabBarControllerObject.selectedIndex = indexOfRequiredTab; //Setting this property changes the selected view controller to the one at the given index in the viewControllers array
}
}
}
Views designed on story board are already initialized, we can access them using the IBOutlet and change their properties.
My iOS app has:
TabBarController
NavigationController1
TableView1
ViewController1 (Details View)
NavigationController2
TableView2
ViewController2 (Details View)
Behavior:
When the app loads, I see the TableView1.
I select an Item in the table, and it takes me via Show (Push) segue the details view 1.
I switch to the second tab on the bottom, and see TableView2.
I select an item and it takes me to details view 2
I navigate back to first tab, and see details view 1
Desired:
When performing last step, I'd like to dismiss the details view and see the first TableView1, and when switching back to second tab, I want that one to be dismissed and to see the table view.
I've tried different combinations of dismissViewControllerAnimated and popToRootViewControllerAnimated but I just don't seem to figure it out.
MainTabBarController.h
#interface MainTabBarController : UITabBarController <UITabBarControllerDelegate>
MainTabBarController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
}
...
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
// NSLog Works fine, and displays information in the output
NSLog (#"%# %lu", tabBarController.selectedViewController.title, tabBarController.selectedIndex);
// None of the lines below achieve the desired result
[viewController.navigationController popToRootViewControllerAnimated:YES];
[viewController dismissViewControllerAnimated:YES completion:nil];
[tabBarController.navigationController popToRootViewControllerAnimated:YES];
[tabBarController dismissViewControllerAnimated:YES completion:nil];
}
One option is to make use of the UITabBarControllerDelegate. Listen for changes to the tab selection. Based on the new tab, get the tab's navigation controller and call its popToRootViewControllerAnimated: method.
Update based on the code added to the question:
The problem is with how you try to pop the view controllers. You want this:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
// NSLog Works fine, and displays information in the output
NSLog (#"%# %lu", tabBarController.selectedViewController.title, tabBarController.selectedIndex);
// If the selected tab's root controller is a navigation controller
// pop to the top view controller in the tab's navigation stack
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)viewController;
[nav popToRootViewControllerAnimated:NO];
}
}
Here is a simple solution for this.
Try to implement the following methods of UIViewContorller
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
Go to your detail-1 view controller and implement the method - (void)viewWillDisappear:(BOOL)animated.
Do a pop for that controller.
Same you should do for the detail-2
Here is the code snippet that will help you.
In Appdelegate.m
#interface AppDelegate ()<UITabBarControllerDelegate>
#property(nonatomic, strong) MainTabBarController *rootTabBarController;
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.rootTabBarController = [[MainTabBarController alloc]init];
self.rootTabBarController.delegate = self;
self.window.rootViewController = self.rootTabBarController;
[self.window makeKeyAndVisible];
}
TabBarController delegate implementation
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSUInteger index = [self.rootTabBarController.viewControllers indexOfObject:viewController];
NSLog(#"Index : %lu", (unsigned long)index);
switch (index) {
case 0:
// pop other tab barcontrollers pushed or modal windows
[self.rootTabBarController flushViewControllerStackForIndex:1];
break;
case 1:
[self.rootTabBarController flushViewControllerStackForIndex:0];
break;
default:
break;
}
}
MainTabBarController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setViewControllers:#[
[[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc]init]],
[[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc]init]]
] animated:YES];
}
-(void)flushViewControllerStackForIndex:(NSUInteger )index {
[[self.viewControllers objectAtIndex:index] popToRootViewControllerAnimated:NO];
}
Here is screenshot in sequence for the sample I ran.
Here is the Sample code.
That should solve your purpose & is the right approach.
Now you may need to fine tune your own logic in flushViewControllerStackForIndex to check if there is just only controller being pushed on stack or a combination of push & modal. So better try to navigate on the Stack & do-a-dismiss-if-a-modal or do-a-pop-if-a-push.
Hope that helps.
You can directly set the view controllers currently on the navigation stack. All you have to is directly set the viewControllers property of the navigation controllers when switch tabs in the tabbar controller.
Set NavigationController1.viewcontrollers = #[tableView1] when you switch to tab1
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've been trying to create a version of this code using a storyboard:
I want to be able to switch between two different detail views, depending on the cell selected in the navigation table. I've tried to implement this by creating a SplitViewManager with a custom setter method that swaps out the detail views each time a different cell is selected. This is the same approach that Apple's sample code uses. The SplitViewManager follows the delegate.
I think my issue is that I haven't connected my splitViewController.delegate to anything, so I can't assign the splitViewManager to anything either. But I can't figure out what I would even connect the delegate to in the storyboard. Please let me know if I'm being an idiot here (almost definitely). Thanks!
My code is below:
DFMAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.splitViewController = (UISplitViewController *)self.window.rootViewController;
self.splitViewManager = (DFMSplitViewManager *)self.splitViewController.delegate;
NSLog(#"split view controller: %#", self.splitViewController); // not null
NSLog(#"split view controller delegate: %#", self.splitViewController.delegate); // is null
NSLog(#"split view manager: %#", self.splitViewManager); // is null.
// But i'm not sure how to assign splitViewController.delegate or splitViewManager in the storyboard.
return YES;
}
DFMSplitViewManager.m:
- (void)setDetailViewController:(UIViewController<SubstitutableDetailViewController> *)detailViewController
{
self.detailViewController = detailViewController;
// Update the split view controller's view controllers array.
// This causes the new detail view controller to be displayed.
UIViewController *navigationViewController = [self.splitViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [[NSArray alloc] initWithObjects:navigationViewController, self.detailViewController, nil];
self.splitViewController.viewControllers = viewControllers;
}
DFMMasterViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DFMAppDelegate *appDelegate = (DFMAppDelegate *)[[UIApplication sharedApplication] delegate];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
if (indexPath.row == 0) {
NSLog(#"clicked cell 1");
DFMDetailViewController *detailViewController = [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[appDelegate.splitViewManager setDetailViewController:detailViewController];
}
else {
NSLog(#"clicked cell 2");
DFMDetailCollectionViewController *detailCollectionViewController = [storyboard instantiateViewControllerWithIdentifier:#"CollectionViewController"];
[appDelegate.splitViewManager setDetailViewController:detailCollectionViewController];
}
}
Turns out you can use the interface builder to add NSObjects to View Controllers. Once I did that, I changed the NSObject's class to DFMSplitViewManager, set it as the SplitViewController's delegate, and it was pretty straight forward from there.
I am facing exactly the same problem as yours. Don't know wether you find out the solution or not, here is the solution I found.
Use following code in your AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
self.detailViewManager = [[DetailViewManager alloc] init];
self.detailViewManager.splitViewController = splitViewController;
self.detailViewManager.detailViewController = splitViewController.viewControllers.lastObject;
splitViewController.delegate = self.detailViewManager;
if ([splitViewController respondsToSelector:#selector(setPresentsWithGesture:)])
[splitViewController setPresentsWithGesture:YES];
return YES;
}
The rest of code is the same as what Apple has provided.
Basically, self.detailViewManager is our split view controller, when you select cell in table, self.detailViewManager will reset the detail view (if I'm not wrong). I'm new to Xcode, so anyone please correct me if I'm wrong.
Here is the solution link, answered by hallmark.
When app is back from the background, I want to push my menu viewcontroller from the navigation controller.
To test — NSString returns the title of my Navigation controller so it should work, but it does not.
It always show the view controller.
P.S. I use storyboard.
In AppDelegate.m:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSString * test = self.window.rootViewController.title; // return title of navigation controller
MenuViewController *mvc = [[MenuViewController alloc] init];
[(UINavigationController *)self.window.rootViewController pushViewController:mvc animated:NO];
}
Download Project
Probably, you should update your UI in - (void)applicationDidBecomeActive:(UIApplication *)application method.
Update.
Also, error is because in your storyboard you present new ViewControllers as Modal, but you should Push them. Then you can return to main menu by calling
[(UINavigationController *)self.window.rootViewController popToRootViewControllerAnimated:NO];
To change presentation style choose segue in your storyboard, choose Attributes Inspector and change style to 'Push'.
Or, if you prefer Modal presentation style, call
[(UINavigationController *)self.window.rootViewController dismissModalViewControllerAnimated:YES];