I have view A, view B and view C. I have pushed view B from view A and push view C from view B.
When user tap on back button in view C, I call popToRootViewControllerAnimated so that user won't see view B at all.
Problem is that if user swipe back in view C, they will still see view B. I don't want user to see view B at all and jump directly to view A. How shall I do?
I use xib currently.
You can change stack of NavigationController when ViewController C appears. Use below code:-
NSMutableArray *aMutArr = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[aMutArr removeObjectAtIndex:aMutArr.count-2];
self.navigationController.viewControllers = aMutArr;
I have removed ViewController B from the stack. So if your user swipes back he will be able to see ViewController A.
you can turn off the swipe back feature
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
in that way the user can only go back by the button that you have provided
Related
Let's say I have 3 controllers (A, B, C). A and C is ViewControllers, B is NavigationController. Normal application flow is A as root view, A present (modal) B, B push C.
What I want is to present C as top view controllers without going through all the animation from A-B-C but still have the hierarchy (means C can go back to A), is it possible?
We can set window rooViewController directly to C but it wont have the hierarchy
EDIT:
Maybe my question isnt clear enough, the main point here is, when I open my app, I want to show C directly but still have A->B->C view hierarchy so I can go back to A via normal pop and dismiss
EDIT2:
I manage to show C with B-C hierarchy, so I can pop back to B from C. Now my problem is how can I present B (NavigationController) from A (ViewController) so when I close B it will **dismiss* to A
EDIT3:
I saw some answer that use NavigationController, it works BUT not what I want because normally from A to B I use modal presentViewController and from B to A I use dismissViewController
EDIT4:
So far what I got is
self.window.rootViewController = vcA;
[self.window makeKeyAndVisible];
[vcA presentViewController:vcB animated:NO completion:nil];
[vcB pushViewController:vcC animated:NO];
this will give correct hierarchy that I want but it give fast animation (a blink) showing A and than C and also give warning Unbalanced calls to begin/end appearance transitions for <vcA: 0x7fcfa0cf9c50>.
EDIT5:
I endup ignoring the warning and stick with my prev answer (but still welcome for another solution). And for the blinking problem I use workaround below
uiview *overlay = [new uiview]; // using vcA.frame
overlay.backgroundColor = white; // I use dominant color of vcC
vcA addSubview:overlay;
self.window.rootViewController = vcA;
[self.window makeKeyAndVisible];
[vcA presentViewController:vcB animated:NO completion:^{
[overlay removeFromSuperview];
}];
[vcB pushViewController:vcC animated:NO];
This will disguise the blinking behavior so no one will notice (I hope :-p)
Use a UINavigationViewController and then call
setViewControllers(_:animated:)
Use this method to update or replace the current view controller stack without pushing or popping each controller explicitly. In
addition, this method lets you update the set of controllers without
animating the changes, which might be appropriate at launch time when
you want to return the navigation controller to a previous state.
If animations are enabled, this method decides which type of
transition to perform based on whether the last item in the items
array is already in the navigation stack. If the view controller is
currently in the stack, but is not the topmost item, this method uses
a pop transition; if it is the topmost item, no transition is
performed. If the view controller is not on the stack, this method
uses a push transition. Only one transition is performed, but when
that transition finishes, the entire contents of the stack are
replaced with the new view controllers. For example, if controllers A,
B, and C are on the stack and you set controllers D, A, and B, this
method uses a pop transition and the resulting stack contains the
controllers D, A, and B.
Let me know if it help you :)
push A and B and C like you normally would but do it by using presentViewController:? animated:NO and pushViewController:? animated:NO -- not animating is the clue
e.g. (mock code)
applicationDidFinishLaunching {
id a = [MyA new]; //root, presents b
id b = [MyA new]; //pushes c you said.. so it is or has a navigationController
id c = [MyA new];
[a presentViewController:b animated:NO];
b.navigationController pushViewController:c animated:NO];
}
To segue to any ViewController you want to: Have you drawn a custom segue in the storyboard by holding down the control key on your keyboard and clicking the ViewController you want the segue to be in? While still holding control, you can drag it to the viewcontroller you want to segue to. After that just let go and XCode will let you choose the type of segue you want: push, modal, or custom.
After that, click the visual segue reference that Xcode creates (looks like a big grey arrow in the storyboard that points to your viewcontrolelrs) and click the attributes inspector. Then look where it says identifier. From there you can name the segue anything you want and reference it programmatically. Just do the above and call the below message and you should be able to go to any viewcontroller when ever you want to.
[self performSegueWithIdentifier:#"ShowViewControllerA" sender:self];
Also, I agree with everyone else saying to set Viewcontroller C as root ViewController in the storyboard. PresentViewController is also a good idea. etc
Follow #Daij-Djan's answer:
applicationDidFinishLaunching {
id a = [MyA new]; //root, presents b
id b = [MyA new]; //pushes c you said.. so it is or has a navigationController
nav d = [[Nav alloc] initWithRoot:b];
id c = [MyA new];
[a presentViewController:d animated:NO];
b.navigationController pushViewController:c animated:NO];
}
Why don't you just present C on top of A with presentViewController?
EDIT:
A -> C:
In vcB I would add a boolean property indicating whether we are in the mentioned flow and present vbB in vcA this way:
// We are in vcA where you want to present vcB and vcC
vcB.transient = YES;
[vcA presentViewController:vcB animated:NO completion:^{
[vcB pushViewController:vcC animated:NO];
}];
C -> A
When you want to go back to vcA, popping vcC will call viewDidAppear in vcB.
// We are in vcB
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(self.transient == YES) {
[self dismissViewControllerAnimated:NO completion:nil];
return;
}
}
With this solution when you go back from vcC to vcA you will temporary see vcB, as we have to wait for viewDidAppear to be called.
EDIT 2:
Alternatively if you don't need to directly go back from vcC to vbA, just use the first piece of code (no transient property required).
Keep A as rootVC(in applicationDidFinishLaunching). So when you open your app it will load A first.
Once A is loaded(in viewDidLoad) call a method to present B(keep animation = NO while presenting).
When B is loaded, call a method to push to C(keep animation = NO while pushing).
Hope this helps!
I have a view controller (in a navigation controller) A that segues to View Controller B, and View Controller C also segues to B (A -> B <- C). View Controller B is used to select a value that is passed back to either A or C, but if I click 'Cancel' or 'Done' it uses the unwind segue to go back to only one controller, whichever was connected last.
You can have an unwind segue return to whichever viewController initiated the segue. All you have to do is to implement the same method you are returning to in all of the viewControllers which segue to viewController B.
So in viewController A and viewController C, implement the following method:
#IBAction func backFromB(segue: UIStoryboardSegue) {
print("Back from B")
}
Then, when you Control-drag from your Cancel button in viewController B to the Exit icon at the top of the viewController, select backFromB from the pop-up.
Then when you run the app and hit Cancel in viewController B, you will return to either viewController A or viewController C (whichever one segued to B). This even works if one segue is a Show (Push) and the other is Modal.
I've got a tabbar controller as a main screen in my app. Some of the tabs got navigation controller embedded.
Here is my problem:
First tab is the initial one when app starts. Under certain conditions user should see second screen B (on navigation stack) immediately after app starts (there is performSegue which triggers in viewWillAppear of first screen). This works as it should. User starts the app and immediately sees the second screen. This also works when user switches to different tab and back. The problem is when user already is on the first tab and taps on it again. Then the stack gets destroyed users sees first screen A which will animate into second screen B in short order. This transition is clearly visible by user.
First tab --> screen A --> screen B --> ...
|
Second tab --> screen T --> screen U --> ...
|
...
So the question is how to prevent this behaviour? User shouldn't see the transition between A and B in this case.
Thanks
Why don't you change your code from Screen A to perfrom segue to next next Screen B unser CERTAIN condition.,
Just use that CERTAINcondition in TabbarController Class ( Subclass of Tabbar) in method like,
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if (tabBarController.selectedIndex == 0)
{
if (CERTAIN condition True) {
UITabBar tabbar = self.tabBarController.tabBar;
NSArray *controlrs = self.viewControllers;
NSMutableArray *controllerCopy = [controlrs mutablecopy];
SCREEN_B_class *bClassObj = . .. //Tab 1 class Just Screen B, not Screen A -> ScreenB
[controllerCopy replaceObjectsAtIndex:[NSIndexSet indexSetWithIndex:0] withObjects:[NSArray arrayWithObjects:bClassObj]]
tabBarController.viewControllers = controllerCopy;
}
}
}
Hope this helps you,
HTH, Enjoy Coding!!
That's the expected behavior of UITabBarController, it will pop the navigation controller to the first view controller in its stack.
If you don't want the screen A to appear under certain conditions when user taps the tab bar item, just remove the first view controller from the navigation stack, doing something like this after pushing from A to B (form instance in the viewDidLoad: of B view controller):
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray: navigationController.viewControllers];
[allViewControllers removeObjectAtIndex: 0];
navigationController.viewControllers = allViewControllers;
This way screen B will be the root view controller of your navigation stack, which is what you want.
I have three UIViewControllers (let's call them A, B and C) in a navigation controller. A can segue into either B or C. B can segue into C. When C closes, I want it to always return to A, i.e. it automatically closes B upon closing C, if opened from B.
Now, I tried using segue unwinding, so that when C closes, B's return method gets called to dismiss the destination controller:
- (IBAction)returnFromC:(UIStoryboardSegue*)segue
{
[segue.destinationViewController dismissViewControllerAnimated:YES completion:nil];
}
I put a breakpoint in this method - it is called. I have verified that the destinationController is indeed B. However, I noticed when the break point hits, C is still visible. After playing from the break point, C does exit as expected, but B is still visible.
Any ideas? Many thanks in advance.
When C closes, I want it to always return to A, i.e. it automatically closes B upon closing C, if opened from B
The simplest solution is: in C's viewDidAppear:, secretly remove B as a child of the navigation controller. In other words, you've got this:
A > B > C
Now you rearrange things so that you've got this:
A > C
Thus, the only thing to go back to from C is A.
It's easy to manipulate the navigation controller's set of children in this way. Just call setViewControllers:animated: (with a second argument of NO).
[But if I've understood your setup properly, another easy way would be to implement the unwind method in A and not B. Then do an unwind segue. We always unwind to the first view controller that contains the unwind method, so that would always be A.]
dismissViewControllerAnimated:completion: is used to dismiss a Modal segue and has no effect for Push segues
As #matt suggested you could just remove B (the middle) view controller from self.navigationController.viewControllers and here's a sample code that you can put in B view controller:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// No need to process if the viewController is already being removed
if (!self.isMovingFromParentViewController) {
// Getting a mutable copy of the viewControllers (can't directly modify)
NSMutableArray *temp = [self.navigationController.viewControllers mutableCopy];
// Removing 'self' -> so A > B > C will become A > C
[temp removeObject:self];
// Setting the new array of viewControllers
self.navigationController.viewControllers = [temp copy]; // getting an immutable copy
}
}
P.S. You could use popViewControllerAnimated: instead of Unwind segue if you're not planning to send the data back.
I have 3 view controllers: A, B and C. A is initial and it has 2 buttons: one is redirecting to B, and second one to C. When B is pushed, and button within B pressed user is redirected to C. Now on navigation bar within C, when I press back button I want to be redirected to A always, and never to B. Is that possible?
This is base functionality I want to achieve:
a)
A->B->C C back to A
b)
A->C C back to A
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[A class]])
{
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
you can achieve this with presentview controller as modal view.first from a if first button is pressed present b view.if secnd button is pressed present c view and in c view controller put back button and add delegates and implement the delegate in a view controller.and you will have to dismiss one view after the other in a view controller.
The only way is replacing the back button by a custom button.
After reading what you said I think you meant implementing custom actions for each button.The way I would do it(In Swift) is this:
#IBAction func pressButton(sender : UIButton){
var SecondView :ViewController2
SecondView = self.storyboard?.instantiateViewControllerWithIdentifier("SecondView") as ViewController2
self.presentViewController(SecondView, animated: true, completion:nil)
self.viewWillDisappear(true)
}
This method I have used a lot and I know there is another way using segues but you still use #IBActions.You just create a new segue(identifier) and in the prepareforSegue method you say what happens when that segue is triggered