Monotouch Crash: Replace UINavigationController A with B and Push UiViewControllers on B - ipad

We have been using Xamarin for a while and it is causing a weird crash while developing the latest iPad app. Following is the problem:
The ipad App uses a story board with 2 Navigation controllers (say A and B) each has a RootViewCOntroller (say A1 and B1), with A as default NavController and is showed on APP start up. There is 2 buttons on A1 , pressing button 1 loads NavController B.Here are the steps that code execute when the button one on NavController A is clicked.
In the Button Event handler , I call a public function in AppDelegate which does the following:
Get the StoryBoard iD and instantiate the NavController B using storyboard.InstantiateViewCOntroller.
Remove the NavCOntroller A from its superview (using UIView.View.RemoveSuperView())
Assign the above reference of B to the appdelegate window's Root view controller (which loads the rootview controller B1 of NavCOntroller B).
It works fine till the above step (I can see the rootviewcontroller's UiView load on iPad).
the next step I am trying to push another UiViewController after Instantiating it.(Because my target is 2 show the second screen in hierarchy) (storyboard.InstantiateViewController(identifier).This should ideally show me the pushed view controller.But the app crashes after this step.!! WEIRD!!!!.
Following is the simplified code:
public void SetNavControllerB(UINavigationController navA, UiStoryBoard StoryBoard)
{
navA.View.RemoveFromSuperView();
this.Window.RootViewController = (UiNavigationController)StoryBoard.InstantiateViewController("VC0");
// 'VC0' is the identifier for NavigationController B with RootViewController as "VC1"
// It works great till the above step
UiViewController vc2 = (UiViewController)StoryBoard.InstantiateViewController("VC2");
((UINavigationController)this.Window.RootViewController).PushViewController(vc2, true);
//The above executes with out any exception, but it crashes later.
}
Pleaseeeee help!!!!!!

If you are modifying the stack in a UINavigationController beyond a push or pop, then you need to replace its entire stack of controllers:
myNavController.SetViewControllers(new UIViewController[] { controller1, controller2 }, true);
If you try to push or pop before a previous push or pop completes, then you will get a crash and a warning in the console output.
PS - I don't think you should need to call RemoveFromSuperview anywhere in your situation, you should just modify the UINavigationController's stack or replace RootViewController on your window.

Related

iOS Storyboards: How to segue to the second view with a back button to the first view, but without displaying the first view

Here is my storyboard configuration:
Navigation Controller -> View Controller A -> Push-> View Controller B
^
|
Modal
^
|
View Controller C
What I want to achieve: When a button is pressed in View C, directly View B will be opened modally (No part of View A is to be displayed). Also, View B will have a navigation back button to View A.
To achieve this,
I set up the illustrated storyboard.
I created a segue between View C and the Navigation Controller of View A/B.
In the 'prepareForSegue' method of View Controller C, I get an instance of View Controller A as the first element in the navigation. In this instance, I set a variable like 'directlyProceedToViewB=YES'.
In the viewDidLoad method of View Controller A, I check the variable 'directlyProceedToViewB' and if it is YES, I call 'performSegueWithIdentifier' to segue to View B
The result is so that, first View A is opened modally and after displaying it a very short time, View B is opened with a push animation (View B can navigate back to View A, which is good). But I do not want View A to be displayed any time at all. How can I achieve this?
EDIT:
To better visualize, I'm adding a screenshot with more example cases to support:
Here are some cases I want to support:
We can start with ViewC, click on 'Modally Display B' which opens ViewB, then click 'Back to A' to navigate back to ViewA, then click on 'Dismiss Modal' on ViewA to go back to ViewC
We can start with ViewD, clcik on 'Modally Display A' which opens ViewA, then click on 'PushB' to open ViewB, then go back and forth between A and B and modally dismiss to ViewD.
First of all, some corrections: those are not views but view controllers. And "view A" is not pushed into the UINavigationController but it's the root.
After that, I suggest making the segue in "view C" an unwind segue and implement the IBAction in "view A" by pushing "view B" with [[self navigationController] pushViewController:bViewController animated:NO].
EDIT (adding some details):
I assume that in ViewControllerA's viewWillAppear you present ViewControllerC in a not animated manner.
Implement an unwinding action like (IBAction)unwindAndThenGoToB:(UIStoryboardSegue *)segue in ViewControllerA.
In the storyboard connect the button in ViewControllerC to the Exit icon and select the previously defined method.
Then implement the method with the push call I wrote earlier.
ps: for documentation there is plenty on Apple's website.
Implement this using delegates.Decalre protocol in which class you want and define those methods and call the methods in the view controller you want.There is no many ways of calling some view and showing back button to go different view.modal view is just a concept.and you can use delegate methods to call whatever class you want.
Here I got a way to do so:-
You need to set no animation for segue from viewC to viewA as shown in below image. Then set a segue identifier for segue from viewA to viewB namely, "viewB" and in your viewA .m file add following code,
- (void)viewDidLoad {
[super viewDidLoad];
// Place your conditional check here.
[self performSegueWithIdentifier:#"viewB" sender:self]; //Will directly lead to viewB and viewA won't be shown as no animation is there from viewC to viewA.
}
And your rest flow be like-wise.
I found the solution myself.
First, I discovered that, my original proposal of
In the viewDidLoad method of View Controller A, I check the variable
'directlyProceedToViewB' and if it is YES, I call
'performSegueWithIdentifier' to segue to View B
works as I desired on iOS 7 but does not work on iOS 8.
So the solution is, in the viewDidLoad method of View Controller A, if 'directlyProceedToViewB' is YES, rather than calling performSegueWithIdentifier, use the following code:
ViewControllerB *destVC = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewControllerBStoryboardID"];
[self.navigationController pushViewController:destVC animated:NO];

Uncertainty with instances in navigation: UITabBarController

In my app, I have a UITabBarController that is set as the initial view controller and has the storyboard ID 'TabBarControl'. TabBarControl has 4 tabs, and off of the second tab(index 1) there is a sequence of UIViewController navigations that goes as such, where VC is ViewController..
UITabBarController > UINavigationController('branchesControl') > VC1('branchesView') > UINavigationController > VC2 > UINavigationController > VC3 > UINavigationController > VC4
I have a UIBarButtonItem named 'Confirm' on the navigation bar in VC4. The Confirm button triggers the following IBAction method:
- (IBAction)confirmClicked:(UIBarButtonItem *)sender
{
//EXECUTE NAVIGATION
UITabBarController * tabControl = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarControl"];
tabControl.selectedIndex = 1;
[self presentViewController:tabControl animated:YES completion:nil];
}
The goal is to navigate from VC4 back to the original 'branchesView'
The problem is, I've noticed that a manual set of a color of a UIBarButtonItem on VC2's navbar resets to a default white after hitting Confirm and then revisiting the view stack. I believe that I am creating multiple instances.
How can I simply navigate back to branchesView, and then VC2 without creating new instances?
PS I'm not sure if the new instance is TabBarController or or branchesView, I just know that it would seem that I am creating multiple instances somehow with the confirmClicked: method.
Instead of going back to the existing branchesView, you are presenting a new UITabBarController with a whole new set of child view controllers which is almost certainly not what you want to be doing.
Instead, you need to pop back to the already existing view controller that you want to get back to. You can do this either by reference or index, and in your case, by simply popping to the root (provided the view controller you want to get to is indeed at the root).
You can simply do:
[self.navigationController popToRootViewControllerAnimated:YES];
Your question also makes it seem like each view controller in the stack is wrapped in a new navigation controller. if that is the case, you will need to fix that as well. You should not nest UINavigationControllers. Instead, have a single one as the root for each tab and each time you want to push a new UIViewController onto the stack, you call:
[self.navigationController pushViewController:viewControllerToPush animated:YES];

ViewController transitions

I'm building an iOS app that uses a lot of web services.
The app has a UItabBarController as its root VC.
In one of my controllers I am pushing a viewController onto a UINavigationController. The ViewController that I am pushing onto the stack is a UITableViewController that has a UITableView with data from the web. I reinstaintiate the controller on each level the user goes down in the data. Then, right at the last level I call a web service and then want the whole UITableViewController to be dismissed and the original view controller to be shown.
This is how it looks:
ViewControllerA (UIViewController) -> user taps a Button - Push ViewControllerB onto it. (UINavigationController).
Once I am done with ViewControllerB I need it to be dismissed and ViewControllerA to be shown with the results.
In ViewControllerB I do something like this in NSNotification method:
if ([errorString hasPrefix:#"No descendants"]){
UINavigationController *navigationController =
[self.tabBarController.viewControllers objectAtIndex:1];
BBSearchViewController *searchViewController =
navigationController.viewControllers[0];
searchViewController.categoryId = self.categoryId;
if (searchViewController.brwosingCategoriesFromSearchPage){
[[BBDataStore sharedDataStore]deRegisterForNotifications:self];
[self.navigationController popToRootViewControllerAnimated:NO];
This causes this error in the console:
> 2014-05-08 09:32:16.306 TestApp[36948:60b] Finishing up a navigation transition
in an unexpected state. Navigation Bar subview tree might get corrupted.
2014-05-08 09:32:16.780 TestApp[36948:60b] Unbalanced calls to begin/end
appearance transitions for <ViewControllerA: 0xd08aa00>.
Can someone tell me what I am doing wrong here?
You're getting these errors because you're attempting one transition before another has finished. In your case, it looks like you're getting the notification before the view has finished appearing and then attempting to pop to root. You should be calling popToRootViewControllerAnimated: using user input (when the user taps a button) or at least after viewDidAppear:.

UIViewController pushed onto UIController embedded in UITabBarController not calling delegates

I have a UITabBarController that has four UIViewControllers. These are set up in the app delegate and everything behaves as expected.
I have set the first UIViewController as the UITabBarControllerDelegate and that works fine too; as tabs are pressed shouldShowViewController fires as expected.
Inside the first UIViewController, the first tab, are buttons. One pushes another UIViewController using the standard:
[self.navigationController pushViewController:vc animated:YES];
This works just fine: the new UIViewController and it's view appear as expected.
However, when I press the tab button the shouldShowViewController function is called (as expected) and passes a reference to the first tab (as expected) but the child UIViewController is nowhere to be found. That is, viewController.navigationController.viewControllers is an empty array (count == 0).
To debug I implemented the UINavigationControllerDelegate and assigned the navigationController to the same class from the UIViewController and tab controller. It fires when called from the tab controller but not the view controller.
I've tried everything I can think of to find a reference to the topmost visible view controller but I'm stumped; it seems to vanish into a black hole. I"m ready to try to keep the stack manually but trying to keep a parallel array with system functionality built into it seems like a setup for a maintenance mess. I am using iOS6.

UIStoryboard popViewController

I'm using a UISegue in my application to automatically transition between viewcontrollers in my application. I'll call them A and B.
It worked as expected, however when I wanted to pop-back to the A from B, I attempted to call
[self.navigationController popViewController] however the B's navigationController property reports null.
As a second attempt I attempted to map a button, to a UISegue back to view controller A.
However this just creates a new ViewController.
As a work around, I ended up doing as a work around was to retrieve the B viewcontroller from the UIStoryboard and calling [A.navigationController pushViewController:B]
At which point, calling [B.navigationController popViewController] worked as expected.
This seems wrong, from a storyboard segue how can I return to the previous view controller?
I don't know about the class of your A and B controllers (whether UIViewController, UITableViewController or UINavigationController), but if you follow the following pattern, it should work.
In an empty storyboard, insert a UINavigationController. This will bring in two windows to the storyboard, linked with an arrow (a segue). The one on the right should be controller A.
In A, let's say, you add a button. The button will push B into the navigation stack.
Then, you add the second controller B, and drag from the button in controller A to controller B, and choose "push" from the popped menu.
If you only use a UIViewController (A) and push B, there is no navigationController to take care of popping.
Hope that help.

Resources