ButtonTap-> Dismiss B, Present C & Don't Show A - ios

Question: How do I dismiss B, Present C, and Not Show A?
So there are three view controllers (A, B, and C). View A presents view B. Tapping a button on B should dismiss B and show C. The transition should appear to be direct from B -> C.
Option 1 - Fail
It can't be a simple segue presenting C because I need B to go away.
Option 2 - Fail
This thread almost answers my question. It suggests using delegation to call a function in A, which dismisses B and presents C. I tried it and it certainly dismisses B and presents C, but even with animation turned off it clearly shows B-> A -> C. The user sees A, so it's no good.
Option 3 - Fail
I also tried using an unwind segue. However, it just unwinds to A and stops. It appears that the presentSegue in A's unwind function fails to present C because it runs too early.
Note:
I'm not using a navigation controller, in case that matters

This will only work when B is wrapped in a navigation controller.
A presents(modal) navcontroller-wrapped-B.
B pushes C
Immediately after pushing(in the next line) set the viewControllers property to and array of just C.
Something like:
self.navigationController!.pushViewController(C, animated: true)
self.navigationController.viewControllers = [C]
Hope this helps.

You might want to reconsider your navigation pattern, as it is not something you can easily do. There's a navigation controller for that purpose, so why not to use it?
If you still want to do it your way, I can only think of adding a screenshot of dismissed view controller while presenting a new one. I'll give an example in Objective C, convert it to Swift yourself if needed.
Add UIViewController+Replace.h file to your project:
#import <UIKit/UIKit.h>
#interface UIViewController (Replace)
- (void)replaceWithViewController:(UIViewController* __nonnull)viewController animated:(BOOL)animated completion:(void (^ __nullable)(void))completion;
#end
Also add UIViewController+Replace.m:
#import "UIViewController+Replace.h"
static UIImage* RenderImageFromView(UIView *view, CGRect frame)
{
UIGraphicsBeginImageContextWithOptions(frame.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[view.layer renderInContext:context];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return renderedImage;
}
#implementation UIViewController (Replace)
- (void)replaceWithViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion
{
UIViewController *parentController = [self presentingViewController];
NSAssert(parentController != nil, #"Cannot replace root view controller");
CGRect bounds = self.view.frame;
UIImageView *captureView = [[UIImageView alloc] initWithFrame:bounds];
captureView.image = RenderImageFromView(self.view, bounds);
[parentController.view addSubview:captureView];
[self dismissViewControllerAnimated:NO completion:^{
[parentController presentViewController:viewController animated:animated completion:^{
[captureView removeFromSuperview];
if (completion)
completion();
}];
}];
}
#end
Then do #import "UIViewController+Replace.h" in B and open view controller C from it's code:
UIViewController *c = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewControllerC"];
// or instantiate it whatever way you want
[self replaceWithViewController:c animated:YES completion:nil]
But again, it is some weird way of doing navigation. After all, modal view controllers are not intended for navigation.

Related

Present nth View Controller without showing its root VC

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!

Navigate to RootViewController from ModalViewController

I have an app with 3 view controllers. A is the root, B is next and C is a modal view that appears / transitions when a button on B is clicked. What I want to do is sometimes move from C to A directly when a button on C is clicked (other times, the C->B transition should happen, which is ok). I've tried various options and incorporated info from different SO posts but in the end, stuck at the scenario where the direct transition from C to A happens but does show B momentarily.
Is there a way I can remove this momentary B appearance altogether. Also, I do need to have animations when the user goes from B to C or from C to B. It is the C to A transition that need not have an animation if there is a limitation there.
Hopefully you can chip in... have copied relevant code and further details below.
ViewControllerC.h:
#protocol ViewControllerCDelegate <NSObject>
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC;
#end
#interface ViewControllerC : UIViewController
#property (nonatomic, weak) id < ViewControllerCDelegate > viewCDelegate;
#end
ViewControllerC.m:
- (IBAction)navigateToViewA:(id)sender
{
[self.viewCDelegate didNavigateBackFromViewC:self];
}
For the B->C and C->B transitions, I've used a separate transitioning controller that is a delegate of ViewControllerB. Also, ViewControllerB is a delegate of UIViewControllerTransitioningDelegate... I've not included those details for the sake of simplicity here but hopefully am able to convey the problem here about the C->A transition.
ViewControllerB.h:
#interface ViewControllerB () <ViewControllerCDelegate>
#end
ViewControllerB.m:
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC
{
__block ViewControllerB *me = self;
[viewControllerC dismissViewControllerAnimated:YES completion:^{
[me.navigationController popToRootViewControllerAnimated:YES];
}];
}
really interesting question #vikram17000. First of all, you might want to try unwind segues, it seems that they can do what you want but I haven't tested that. It appered you were going the programmatic route, and here's what i found. I split (what I called) the 'back' and 'home' actions into two separate actions. Here's my ViewControllerC code:
-(IBAction)goBack:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
-(IBAction)goHome:(id)sender
{
[self.delegate ViewControllerCWillGoHome:self];
[self dismissViewControllerAnimated:YES completion:nil];
}
ViewControllerB is the delegate that gets the -...WillGoHome, and it popsToRootVC before VC C dismisses. This gets the behavior we want, C animates out and A is left - yay! Unfortunately there's a catch -
Unbalanced calls to begin/end appearance transitions for ViewControllerB: 0x7fc3c857ca80.
So is it possible? Yes. Does the os like it? No it does not. Hopefully this helps you get going though.
I've created sample project for your question.
There is protocol GoBackProtocol which contains method
-(void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button;
A and B controllers adopt GoBackProtocol. B controller implements -controller:didPressBackButton: method and decides based on button parameter what to do: hide presented controller with animation or hide it without animation and forward call to his delegate, which is controller A.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
ViewControllerC *modalController = (ViewControllerC *)controller;
if (button == modalController.buttonB)
{
[controller.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
else if (button == modalController.buttonA)
{
[controller.presentingViewController dismissViewControllerAnimated:NO completion:nil];
[self.delegate controller:self didPressBackButton:nil];
}
}
Controller A implements -controller:didPressBackButton: as well and pops viewController B.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
[self.navigationController popViewControllerAnimated:YES];
}
All Together it makes desired effect.
Please forget all custom protocol solutions here. Apple says:
The presenting view controller is responsible for dismissing the view controller it presented.
IMO you have at least two options here:
1) You could add the close button on C to public interface like
#property (nonatomic, strong, readonly) UIButton *closeButton;
Set the target differently and use a combination of dismissViewControllerAnimated:completion: and popToRootViewControllerAnimated: as in your method didNavigateBackFromViewC
2) You could use self.navigationController.presentingViewController in C to get access to the non-modal viewcontrollers.
As non-official option you could set the viewcontrollers array on a UINavigationController instance directly. Then you will get a Push Animation from C to A.
I haven't fully testing this out yet but results so far seem encouraging. Given that I wanted to be able to transition to B or C from A, and that B and C could navigate between each other, I've started playing around with making ViewControllerA as the container view controller for ViewControllerB and ViewControllerC. That allows me easy access to B / C from A, as well as the ability to transition between B and C seamlessly using custom transitions.

Problems dismissing and representing UIImagePickerController camera iOS8.4

EDIT Problem reinstated from scratch.
I have a ViewController A which has a Navigation Bar Button which presents an UIImagePickerController of sourcetype UIImagePickerControllerSourceTypeCamera, we'll call this ViewController B. In the didFinishPickingMediaWithInfo: method of A, I then present ViewController C.
The problem now starts here. When I finally reach C, we can see that view stack is clearly:
A -modal-> B -modal-> C
From C I then have a "Back" button present on the navigation bar which should take me back to B. However, since B is an UIImagePickerController, I cannot reuse it and it must be dismissed. So, to make this happen, I currently have the following method executing for that "Back" button on C:
- (IBAction)backOnPost:(UIBarButtonItem *)sender
{
[self.view endEditing:YES];
UINavigationController *LogControl = [self.storyboard instantiateViewControllerWithIdentifier:#"LogControl"];
RGLogViewController *logView = (RGLogViewController *)LogControl.topViewController;
[UIView animateWithDuration:0.25 animations:^{
self.presentingViewController.view.alpha = 0;
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil];
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
} completion:^(BOOL finished){
[logView setBoolBackPushed];
}];
}
The above code works in that it takes me back to A by dismissing B and C. However, you can still see the transition because of a bit of "black-screening" from the dismissal of the two ViewControllers. You can see in the completion block [logView setBoolBackPushed];, this sets a static BOOL variable to true so that the beginning of A's viewWillAppear: presents a new UIImagePickerController immediately - the method looks like this:
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"HERES postBackButtonPushed:%hhd", postBackButtonPushed);
if(postBackButtonPushed == true)
{
self.view.hidden = YES;
self.navigationController.navigationBar.hidden = YES;
self.tabBarController.tabBar.hidden = YES;
UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.delegate = self;
[self presentViewController:imagePicker animated:NO completion:^{}];
}
This is currently how I am getting the following desired effect: Go from A to the camera (B), to C and then back to a new camera which is our new B. This is achieved w/ almost perfect seamless transitions.
Im still having problems with being able to slightly see the transitions. Another problem is the timing. This collaboration of dismissing and presenting ViewControllers takes more time than I would like it to. It's almost instantaneous, but I would like something better. What should I do and am I doing something wrong?
there are some possible cases that cause your problem :
presenting B after A and then presenting C is a bit strange behaviour for both UX/UI and by code. Either push B from A and then present C over B OR dismiss A, present B, dismiss B, present C.
there are completion methods and you are not using them. your code show that you call dismissViewControllerAnimated twice for C and B for ex. you should handle presenting and dismissing order. I think iOS 8.2+ or 8.3+ start to handle them more efficiently. And if there is an error, it will crash after this version.
imagepicker controller cause this error while dismissing VCs in different ways. For ex. showing alert popup before presenting a VC can cause also a crash.
I believe this will work for you.
Do!
[self.presentingViewController dismissViewControllerAnimated:NO completion:^{
[RefrenceOfPreviousViewController dismissViewControllerAnimated:YES completion:^{
}];
}];
Instead of
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil];

Modal View Controller, dismiss and pop back to view controller

Im trying to dismiss a ModaViewController called C, back to A. The ModalViewController is presented in B. Therefore the Navigation flow is A->B - (present ModalView) -> C. I am able to dismiss the ModalViewController back to B, but I am unable to pop back to A in the completion bracket. This is the code I have tried:
[self dismissViewControllerAnimated:YES completion:^{
[self.navigationController popToViewController:[[self.navigationController viewControllers] objectAtIndex:0] animated:YES];
}];
The ModalViewController is dismissed but does not pop back to A. I call this block of code in an IBAction.
Any advice?
On a second note, when I dismiss the ModalViewController all my UIPickers in Controller B are empty/ deallocated. I am using ARC as well.
The problem with your code is that self.navigationController will be nil. If you have a controller (A) embedded in a navigation controller, and that controller pushes to another controller (B) which then presents your last controller (C), then you need to do something like this,
-(IBAction)dismissToBThenPop:(id)sender {
UINavigationController *nav = (UINavigationController *)self.presentingViewController;
[self dismissViewControllerAnimated:YES completion:^{
[nav popViewControllerAnimated:YES];
}];
}
Even though you present C from B, the actual presentingViewController will be the navigation controller. This code will dismiss C then pop B, but you will see B for an instant before ut pops back to A. If you don't want to see this, then you should use an unwind segue to go directly back to A from C.
Your second problem about the pickers being empty and deallocated should not be happening under the scenario that you say you have. You will have to provide more information about what you're doing in B to solve that problem.
Create a protocol in ModalViewController, let's say ModalViewControllerDelegate with a method -(void)dismissTheModal, and make B implement this protocol. Before showing the ModalViewController, do modalViewController.delegate = self. When you're IBAction is called, do [self.delegate dismissTheModal], and in controller B you should do :
-(void)dismissTheModal {
[self dismissViewControllerAnimated:YES completion:^{
[self popViewController];
}];

chaining uiviewcontroller delegates

i have a uivewcontroller (let's call it A) that loads uiviewcontroller (B) setting it up as a delegate which i use to close B and continue code on A. There is also a scenerio where B leads to another uiviewcontroller C (again with a delegate). When C is closed i use it's delegate to return to B but in this scenario i also want B to be immediately dismissed and the code to return to A. Now, B->A works, and C->B works but doing C->B->A fails at B with an error:
"attempt to dismiss modal view controller whose view does not currently appear" it appears to be trying to dismiss view C again.
Both viewcontrollers are being dismissed with this code (though the code sits in different uiviewcontrollers)
[self dismissViewControllerAnimated:YES completion:nil];
Am I using delegates correctly for what i want, or should i be using a different process?
Code for option 1 (A->B, B<-A):
A -> B
scorer_turn *st = (scorer_turn *) segue.destinationViewController;
st.st_delegate=self;
st.league = _match.league;
st.match = _match;
st.leg = _leg;
st.set = _set;
B -> A
-(void)closeView{
[_st_delegate scorer_turn:self didFinish:YES];
}
-(void)scorer_turn:(scorer_turn *)controller didFinish:(BOOL)finish{
[self dismissViewControllerAnimated:YES completion:nil];
}
Code for option 2 (A->B, B->C, C->B->A):
as above plus:
B -> C
matchSummaryViewController *ms = (matchSummaryViewController *) segue.destinationViewController;
ms.match = _match;
ms.oneScreen = NO;
ms.delegate = self;
[[segue destinationViewController] setManagedObjectContext:self.managedObjectContext];
C -> B, B -> A
in C:
[_delegate matchSummaryViewController:self didFinish:YES];
in B:
[self dismissViewControllerAnimated:YES completion:nil];
[_st_delegate scorer_turn:self didFinish:YES];
in A (this is where the error occurs):
-(void)scorer_turn:(scorer_turn *)controller didFinish:(BOOL)finish{
[self dismissViewControllerAnimated:YES completion:nil];
}
If ViewController B is a modalViewController, and ViewController C is pushed onto the same modalViewController, then calling
[self dismissViewControllerAnimated:YES completion:nil];
should dismiss the entire modal view - as in, it'll dismiss C and skip B all-together (B wont even appear), which would cause problems if B tries to also call dismissViewControllerAnimated:completion: since there is no more modal view to dismiss.
Now, if you have some other set-up, such as B being a modal which, when pushing C, it actually dismisses itself and launches a new modal view (this would happen if you always use the presentViewController:animated:completion function), then you have other design problems - you shouldn't be swapping modal views like that, instead use
[self.navigationController pushViewController:C animated:YES]
to show the view and
[self.navigationController popViewControllerAnimated:YES]
to dismiss View C and return to View B.
This is assuming of course that you're using a navigationController to manage your views (which you typically should be doing).
If on the other hand you aren't using modal views at all, then dismissViewControllerAnimated:completion: isn't what you want to use at all.

Resources