I have a come across a piece of code to pop to a specific viewcontroller in a navigation stack as below
for (UIViewController* viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
The objective is to pop to MyGroupViewController. But I am not understanding this line of code.
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
Whats exactly happening here. I don't think a new instance of MyGroupViewController is being created here.
//This for loop iterates through all the view controllers in navigation stack.
for (UIViewController* viewController in self.navigationController.viewControllers) {
//This if condition checks whether the viewController's class is MyGroupViewController
// if true that means its the MyGroupViewController (which has been pushed at some point)
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
// Here viewController is a reference of UIViewController base class of MyGroupViewController
// but viewController holds MyGroupViewController object so we can type cast it here
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
Also you can do like this
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
[self.navigationController popToViewController:viewController animated:YES];
}
Swift code
//Itrate through all the view controllers in navigation stack
for vc in self.navigationController!.viewControllers {
// Check if the view controller is of MyGroupViewController type
if let myGropupVC = vc as? MyGroupViewController {
self.navigationController?.popToViewController(myGropupVC, animated: true)
}
}
The view controllers of a navigation controller stack are being enumerated. Since these view controllers can be of any kind (but will always inherit from UIViewController), the generic UIViewController is used. However, the compiler will not know what type that view controller is, so it is being casted to a MyGroupViewController. When that happens, the compiler knows what the type of class and you can send it messages that only apply to that class.
In this case it is kind of unnecessary, as it could be simplified to this:
(UIViewController* viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
[self.navigationController popToViewController:viewController animated:YES];
}
}
Short answer: it changes a variable type to the type specified in the parentheses to avoid compiler warnings.
for (int m=0; m<[self.navigationController.viewControllers count]; m++) {
if([[self.navigationController.viewControllers objectAtIndex:m] isKindOfClass:[MyGroupViewController class]]) {
MyGroupViewController * groupViewController = [self.navigationController.viewControllers objectAtIndex:m];
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
- (void) RetunToSpecificViewController{
for (UIViewController *controller in self.navigationController.viewControllers) {
if ([controller isKindOfClass:[AnOldViewController class]]) {
//Do not forget to import AnOldViewController.h
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
On Swift
func RetunToSpecificViewController()
{
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController]
self.navigationController!.popToViewController(viewControllers[viewControllers.count
- 5], animated: true)
}
We have write a better tutorial on that , You can check
https://appengineer.in/2014/03/13/pop-to-specific-view-controller-in-ios/
Related
I am debugging legacy code which is always fun. The old code tried to mock the splitView delegate methods, causing all sorts of issues - mainly crashing: on a Plus device in Portrait, rotating to landscape caused the crash - if there was no detail view set, old code attempted to create one in a dodgy hack and it was just useless...
My app is UISplitViewController based, where I have a navigation stack in both master and detail sides of the splitView.
By reading though SO and using this example and was able to implement UISplitViewController delegate methods and everything is working correctly in regards to rotation, and showing the correct master/detail views when appropriate. Here is my implementation: (apologies for wall of code snippets)
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]]
&& [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[AECourseHTMLTableViewController class]]
&& ([(AECourseHTMLTableViewController *)[(UINavigationController *)secondaryViewController topViewController] htmlContentEntry] == nil)) {
// If the detail controller doesn't have an item, display the primary view controller instead
return YES;
}
return NO;
}
And the other splitView delegate method - see comments in code for where I'm stuck.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:courseViewController.currentIndexPath];
}
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
if (!self.splitViewController.isCollapsed) {
UINavigationController *navController = self.splitViewController.viewControllers.firstObject;
AEContentMenuTableViewController *contentMenuVC = navController.viewControllers.firstObject; // This controller needs to be master in Landscape
NSMutableArray<UIViewController *> *controllers = [navController.viewControllers mutableCopy]; // Contains 3 controllers, first needs removed
NSMutableArray *toDelete = [NSMutableArray new];
for (UIViewController *viewController in controllers)
if ([viewController isKindOfClass:[contentMenuVC class]] || [viewController isKindOfClass:[AECourseHTMLTableViewController class]]) {
[toDelete addObject:viewController]; // Remove first VC, so master should become AEContentMenuVC?
break;
}
// Remove the object
[controllers removeObjectsInArray:toDelete];
// Set viewControllers
navController.viewControllers = controllers;
}
return navController;
}
AECourseHTMLTableViewController has next/prev buttons to select the next row in the tableview of the tableview menu class class (AEContentMenuTableViewController). I have a delegate function which can tell me the current indexPath in which AECourseHTML... is using from AEContentMenu..., and when calling it, it selects the menu tableview row and instantiates a new AECourseHTML... and pushes it.
This is where I'm stuck. In Portrait, pressing next/prev is fine, it selects the correct row and works as expected. But once I rotate the device, both master and detail views show the detail view. I can press "Back" on the master view, and it takes me to the correct AEContentMenu... class. As noted in the code snippet comments, I need to remove a ViewController from the master stack (the first object actually), and AEContentMenu... should become the first object of that stack - so when rotating, that should be the master view.
Apologies for such a long post, I've been banging my head with this for weeks now and I want to include as much info as possible in this question. Thanks in advance.
I found a solution which works well for my use cases. It may not be the cleanest code, but I'm happy with what I've got.
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
remains unchanged. I have updated my splitViewController:separateSecondaryViewControllerFromPrimaryViewController: delegate method with the solution. Any feedback is welcome.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Return CourseVC
UINavigationController *navController = splitViewController.viewControllers.firstObject;
UIViewController *viewController;
for (viewController in navController.viewControllers) {
if ([navController.viewControllers.lastObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
return viewController;
} else {
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
// If next/prev has been tapped, configure current ContentHTML
if (self.currentContentHTML) {
[self configureViewController:courseViewController entry:self.currentContentHTML indexPath:courseViewController.currentIndexPath];
} else {
// Create new ContentHTML from first row of AEContentMenuVC
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return navController;
}
}
}
return navController;
}
Your top if statement should return nil. Since you were returning the nested navigation controller you were missing out on the default behaviour of popping the master navigation's top controller which is required so it can then be placed on the right.
The default behaviour will find that nested nav controller and pop it. However the reason you still need to search for it yourself is if it isn't there then you need to load the detail nav from the storyboard as you have done.
There are five controllers here, AViewController, BViewController, CViewController,DViewController,EViewController,controllers here,
A present---> B
B present---> C
C push--->D
D push--->E
Now, if I want to go back from EViewController to AViewController in one step, what code should I write?
[self.navigationController popToRootViewControllerAnimated:animated];
1) Getting the desirable ViewController as Below
for (id controller in [self.navigationController viewControllers])
{
if ([controller isKindOfClass:[AViewController class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
2) Here you have A,B,C,D,E Controllers. means A would be on 1 Position so what can you do
you can hard wired the Index as Below
[self.navigationController popToViewController:[[self.navigationController viewControllers] objectAtIndex:0] animated:YES];
3) Pop to the first viewController or rootViewController
[self.navigationController popToRootViewControllerAnimated:animated];
Use unwind segues.
In AViewController add bellow code
-(IBAction)prepareForUnwind:(UIStoryboardSegue *)segue {
}
Go to User Interface of EViewController and Ctrl-drag from the button (you want set action) to the “Exit” outlet, you will see a modal popup.
You can do it recursively with generic solution. First of all you should have reference to A's navigation controller and then you should write recursive method to get active navigation controller like :`
-(UINavigationController*)getActiveNavigationController : (UINavigationController*)navigationController {
if ([navigationController.presentedViewController isKindOfClass:[AViewController class]]) {
return [self getActiveNavigationController:(UINavigationController*)((AViewController*)navigationController.presentedViewController)
];
}
if ((UINavigationController*)navigationController.presentedViewController == nil) {
return navigationController;
}
return [self getActiveNavigationController:(UINavigationController*)navigationController.presentedViewController];
}
`
After that you should write method like
-(void)getInitialScreen:(UINavigationController*)AViewControllerNavigationController {
if ([AViewControllerNavigationController.presentedViewController isKindOfClass:[AViewController class]]) {
return;
}
UINavigationController *navigation = [self getActiveNavigationController:AViewControllerNavigationController];
[navigation dismissViewControllerAnimated:YES completion:^{
[self getInitialScreen:AViewControllerNavigationController];
}];
}
finally after you wrote those 2 methods. You can call them like below and you can always get AViewController
[self getInitialScreen:AViewControlelrnavigationcontroller];
[AViewControlelrnavigationcontroller popToRootViewControllerAnimated:YES];
I have a NavigationController with different ViewControllers. Sometimes I want to push a ViewController to NavigationController, which is already included.
In this case I want to move to the existing from TopController, and pop all other ViewControllers between top and the existing one.
Is there a way to give a ViewController a special id (for example a NSString), to find him later in "NavigationController.viewControllers"?
Or should I use a seperate Dictionary to manage my ViewControllers?
Or is there a better way, I dont consider.
best regards
On your appDelegate, you can store all your ViewController and when you want to pop call :
[self.navigationController popToViewController:yourViewController animated:YES];
Or you can try to search
NSArray *viewControllers = self.navigationController.viewControllers
for (UIVIewController *anVC in viewControllers) {
if (anVC isKindOfClass:[yourController class] {
[self.navigationController popToViewController:anVC animated:YES];
break;
}
}
Hope it will help you.
Note : As the view controllers are the same class, most times
Add strUniqueID property on each controller while pushing view controller to navigation controller don't forget to set it
Now you know which unique ID's you want remove, so find it
//Firstly find your viewController
for(id viewcontroller in self.navigationController.viewControllers)
{
//For finding specific viewController use isKindOfClass
if(viewcontroller isKindOfClass:[YourViewControllerNameHere Class])
{
//Now find UniqueIDHere
YourViewController *objYourViewController = (YourViewController *)viewcontroller
if(YourViewControllerNameHere.strUniqueID isEqualToString:removeUniqueIDHere])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
else if(viewcontroller isKindOfClass:[YourDifferentViewControllerNameHere Class]) //Different ViewControllers here like this
{
//Now find UniqueIDHere
YourDifferentViewController *objYourDifferentViewController = (YourViewController *)viewcontroller
if(YourDifferentViewController.strUniqueID isEqualToString:removeUniqueIDHere])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
}
Try this :
//Firstly find your viewController
for(id viewcontroller in self.navigationController.viewControllers)
{
//For finding specific viewController use isKindOfClass
if(viewcontroller isKindOfClass:[YourViewControllerNameHere Class])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
Swift 5
let vc = navVC.viewControllers.first(where: { $0.hasKey })
In the appDelegate, I want to do something in case the visible view controller is kind of class MyViewController. Then I want to check the property myVar that is defined in the MyViewController class. This is my code:
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if (vc.myVar == "foo") {
// do something
}
}
I have an error saying: Property myVar not found on object type of 'UIViewController *'
How do I tell the code that I'm sure now that vc is a type of MyViewController class?
What you need to do is casting
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if ([(MyViewController *)vc myVar] == "foo") {
// do something
}
}
You are sure that vc is an MyViewController object, but in the next line vc is still considered as a UIViewController. You can create a MyViewController pointer or cast it automatically :
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
MyViewController * myViewControllerVc = (MyViewController *)vc;
if (myViewControllerVc.myVar == "foo") {
// do something
}
}
or
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if (((MyViewController *)vc).myVar == "foo") {
// do something
}
}
I tend to use the first solution when i will use more than once the custom class properties, otherwise i use the second one.
I do it with code:
NSArray *viewControllersFromStack = [self.navigationController viewControllers];
NSMutableArray *viewControllersFromStackMutable = [NSMutableArray arrayWithArray:viewControllersFromStack];
NSMutableArray *viewControllersToRemove = [[NSMutableArray alloc]init];
for (UIViewController *currentVC in viewControllersFromStack)
{
if ([currentVC isKindOfClass:[TalksViewController class]])
{
[viewControllersToRemove addObject:currentVC];
if (viewControllersToRemove.count == 2)
{
UIViewController *oneVCtoRemove = [viewControllersToRemove objectAtIndex:0];
[viewControllersFromStackMutable removeObject:oneVCtoRemove];
[self.navigationController setViewControllers:viewControllersFromStackMutable];
}
}
}
Problem is that I have reference to removed VC's in navigation Item. How to fix it?
When you want to remove a view from the navigation stack you can simply just call this method on the navigation bar to pop the view from the stack:
[self.navigationController popViewControllerAnimated:YES];
To pop an external view use
for(UIViewController *currentVC in viewControllersFromStack)
{
if([currentVC isKindOfClass:[TalksViewController class]])
{
[currentVC.navigationController popViewControllerAnimated:YES];
}
}
The above answer is correct.
I have 'A' as rootview controller. 'B to F' are other view controllers. From 'F', if I wanted to go directly to 'A', it is as under.
[self.navigationController popToRootViewControllerAnimated:YES];
BUT if I wanted to jump to 'B' then the code in answer is helpful. I only changed the array of view controllers to run reverse with 'reverseObjectEnumerator' and Animated to NO with 'popViewControllerAnimated:NO'. the Code is as under
NSArray *viewControllersFromStack = [self.navigationController viewControllers];
for(UIViewController *currentVC in [viewControllersFromStack reverseObjectEnumerator])
{
if(![currentVC isKindOfClass:[A class]] && ![currentVC isKindOfClass:[B class]])
{
[currentVC.navigationController popViewControllerAnimated:NO];
}
}