TableViewController Context:
Here's my app:
Tab 1: NavigationController -> ViewController
Tab 2: SplitViewController -> NavigationController -> Master : TableViewController ->TableViewController
-> NavigationController -> Detail : TableViewController -> TableViewController
Tab 3: NavigationController -> ViewController (I'm Here)
How do I pop to the root of each tab from a method in the tab 3 (basically a logout button)?
// Based on UIviewController tag you can pop to navigate
// try this..
MyClass *obj = [self.navigationController.viewControllers objectAtIndex:0];
[self.navigationController popToViewController:obj animated:YES];
I managed to accomplish what I was trying to do, here's the code for others looking for the answer :
- (void) logout{
for(UIViewController *viewController in tabBarController.viewControllers)
{
if([viewController isKindOfClass:[UINavigationController class]]){
[(UINavigationController*)viewController popToRootViewControllerAnimated:NO];
}
else if([viewController isKindOfClass:[UISplitViewController class]]){
UISplitViewController *splitView = (UISplitViewController *)viewController;
for (UIViewController *navControllerInSplit in splitView.viewControllers) {
if([navControllerInSplit isKindOfClass:[UINavigationController class]]){
[(UINavigationController*)navControllerInSplit popToRootViewControllerAnimated:NO];
}
}
}
}
}
Add following code for pop to rootview for UINavigationController where UITabbarController
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if ([self.tabBarController.selectedViewController isKindOfClass:[UINavigationController class]])
{
[(UINavigationController*)self.tabBarController.selectedViewController popToRootViewControllerAnimated:YES];
}
}
Reference: View Controller Catalog for iOS
A split view controller must always be the root of any interface you
create.
In other words, you must always install the view from a UISplitViewController object as the root view of your application’s window.
The panes of your split view interface may then contain navigation
controllers, tab bar controllers, or any other type of view controller
you need to implement your interface.
Split view controllers cannot be presented modally.
So, what you are attempting is incorrect, and in fact your app may get rejected by Apple.
Related
I have this navigation stack
RootVC ---> VC1 --> (presenting)-> ModalVC
and I have VC2 (not in navigation stack).
When presenting ModalVC, I want to click on button in my ModalVC to dismiss ModalVC, then push VC2 into the navigation stack after VC1 at one click. It should look like this:
RootVC ---> VC1 ---> VC2
I tried a lot of methods to make it, but pushing event fire only, when I return to my RootVC.
I tried to make it with delegates:
In ModalVC on click:
[self dismissViewControllerAnimated:YES completion:^{
if ([self.delegate respondsToSelector:#selector(dismissAndPush:)]) {
[self.delegate performSelector:#selector(dismissAndPush:) withObject:VC2];
}
}];
In VC1:
- (void)dismissAndPush:(UIViewController *)vc {
[self.navigationController pushViewController:vc animated:NO];
}
Please help to understand this behavior. Where is my mistake?
From Apple Documentation:
The presenting view controller is responsible for dismissing the view
controller it presented.
So, VC1 should be dismissing the ModalVC, try to do this
ModalVC on click:
if ([self.delegate respondsToSelector:#selector(dismissAndPush:)]) {
[self.delegate performSelector:#selector(dismissAndPush:) withObject:VC2];
}
In VC1:
- (void)dismissAndPush:(UIViewController *)vc {
[self dismissViewControllerAnimated:YES completion:^{
[self.navigationController pushViewController:vc animated:NO];
}];
}
Error has been in other. If Im right understand: some animations before dismissing presented view controller are blocking animations in navigation stack. I solved this problem with 2 ways:
1) deleteting or set right animations before dismissing
2) use setViewControllers in navigation controller (I select it)
- (void)dismissAndPush:(UIViewController *)vc {
[self dismissViewControllerAnimated:NO completion:^{
NSMutableArray *mutableControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
NSArray *controllers = [mutableControllers arrayByAddingObject:vc];
[self.navigationController setViewControllers:controllers animated:NO];
}];
}
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
Hello I am quite new to iPhone development (working with iOS 7). Basically I want my application to respond to screen rotations (i.e. changing the screen orientation) My project uses both UITabBarController and UINavigationController. However when I rotate the device it won't call the "shouldAutorotate" function in a ViewController called LoginView.m.
Therefore I have followed the answer in here up to the point where it subclasses both UITabBarController and UINavigationController. Can someone explain to me how to add (i.e reference it)the orientation behaviour within the LoginView.m class or to the entire project.
Your help is greatly appreciated.
Thanks
In your subclassed UItabbarcontroller
- (BOOL)shouldAutorotate {
UINavigationController *navView = (UINavigationController *)[self selectedViewController];
//Get the selected current tab viewcontroller. I guess you are having a Navigationcontroller
UIViewController *vc = [navView.viewControllers objectAtIndex:0];
//Fetch root viewcontroller of your navigationcontroller
return [self checkOrientationForViewController:vc];
}
-(BOOL) checkForViewsForViewController : (UIViewController *)vc{
if([vc isKindOfClass:[FirstViewController class]]){
FirstViewController *vc1 = (FirstViewController *)vc;
return [vc1 shouldAutorotate];
}
if([vc isKindOfClass:[SecondViewController class]]){
SecondViewController *vc2 = (SecondViewController *)vc;
return [vc2 shouldAutorotate];
}
if([vc isKindOfClass:[ThirdViewController class]]){
ThirdViewController *vc3 = (ThirdViewController *)vc;
return [vc3 shouldAutorotate];
}
return YES;
}
In respective viewcontrollers implement the shouldAutorotate method
Instead of using Delegation you can also use
- (NSUInteger)supportedInterfaceOrientations
UINavigationController and UITabBarController ask their child view controllers which interface orientations they support. So you can implement "- (NSUInteger)supportedInterfaceOrientations" in your LoginView.m and return the appropriate interface orientations.
You also need to edit your Info.plist and add the supported interface orientations their. You can do this with Xcode by opening the project view. Also have a look at Apples documentation on supporting interface orientations
Instead of subclassing, is worth to know that there are also delegate methods, in UINavigationcontroller and UITabbarController that make you handle rotations at runtime:
- (UIInterfaceOrientation)navigationControllerPreferredInterfaceOrientationForPresentation:(UINavigationController *)navigationController
Ref doc.
- (NSUInteger)tabBarControllerSupportedInterfaceOrientations:(UITabBarController *)tabBarController
Ref doc.
Evenf if Apple has removed the advice to do not subclass Nav and Tabbar controller, using delegation will be the most reliable solution over time
I am doing an iPad app with UISplitViewController. I want to open a modalViewController in the masterViewController itself. When I load my view controller modally, it takes a whole screen to present it.
Here it is my code, which is in my masterViewController.m to present the new viewController modally
- (void)addNewContactButtonPressed:(id)sender {
AddOrEditContact *addContact = [self.storyboard instantiateViewControllerWithIdentifier:#"AddOrEditContact"];
addContact.screenMode = addMode;
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:addContact];
[self.navigationController presentViewController:navigationController animated:YES completion:nil];
}
I want to load a new viewController modally inside the masterViewController. Any help would be appreciated.
You can't present a modal viewController over the masterViewController only, but you can add a childView controller to the masterViewController nd perform your own animation to present it
- (void)addiewControllerToHierarchy:(UIViewController *)viewController
{
[self addChildViewController:viewController];
[self.view addSubview:frontViewController.view];
if ([viewController respondsToSelector:#selector(didMoveToParentViewController:)])
{
[viewController didMoveToParentViewController:self];
}
}
and to remove
- (void)_removeViewControllerFromHierarchy:(UIViewController *)viewController
{
[viewController.view removeFromSuperview];
if ([viewController respondsToSelector:#selector(removeFromParentViewController)])
{
[viewController removeFromParentViewController];
}
}
this example doesn't have animation and probably you need to adjust the frame of the view etc... but I hope could help you
I have a TabBarController with three tabs. The views for all tabs were embedded in their own navigation controller, except one, the Map view.
To navigate from one of the other views to the Map view and pass data I used:
- (IBAction)mapButton:(id)sender {
MapViewController *destView = (MapViewController *)[self.tabBarController.viewControllers objectAtIndex:0];
if ([destView isKindOfClass:[MapViewController class]])
{
MapViewController *destinationViewController = (MapViewController *)destView;
destinationViewController.selectedTruck = _truck;
}
[self.tabBarController setSelectedIndex:0];
}
And it was working. Now I need to embed the Map view in a navigation controller as well, to add a detail view, but when I do no data is passed, it only goes to the Map view.
Can anyone see what am I missing?
[self.tabBarController.viewControllers objectAtIndex:0] is no longer an instance of MapViewController. It is a UINavigationController with a MapViewController as its root view controller.
You could access the MapViewController via the UINavigationController like so but all these type casting assumptions are messy -
- (IBAction)mapButton:(id)sender {
UINavigationController *navigationController = (UINavigationController *)[self.tabBarController.viewControllers firstObject];
if ([navigationController isKindOfClass:[UINavigationController class]])
{
MapViewController *rootViewController = (MapViewController *)[navigationController.viewControllers firstObject];
if ([rootViewController isKindOfClass:[MapViewController class]])
{
MapViewController *destinationViewController = (MapViewController *)rootViewController;
destinationViewController.selectedTruck = _truck;
}
}
[self.tabBarController setSelectedIndex:0];
}
Instead, a better design would be to keep a reference to the MapViewController (as a property) when you set up the tab bar. That way you would simply call self.destinationViewController.selectedTruck = _truck; instead.