I have a custom UIViewController that's being displayed modally. I'm calling it mainMenu.
It has its own cute little transition animation it does to make its views slide off screen. When I want to dismiss it, I want to call the custom animation and then actually dismiss it once that's done. It seems like this should work:
- (void) dismissCustomViewController {
[mainMenu slideMenuPanelsAway];
[self dismissViewControllerAnimated:YES completion:nil];
}
However, this makes the view controller vanish instantly, before I get to see the custom slidey stuff.
What's the right way to make the view controller wait until the menus are gone before vanishing?
I've tried a bunch of things. I only found one way to make it work:
- (void) dismissCustomViewController {
[mainMenu slideMenuPanelsAway];
[self performSelector:#selector(dismissController) withObject:nil
afterDelay: 2.0f];
}
(I wrote a custom method called dismissController just to make the selector easier to use, it basically just calls [self dismissViewControllerAnimated:YES completion:nil];.)
It just seems awful kludgey to use a manual delay setting instead of actually basing it on the completion of the animation. There's got to be a better way, doesn't there?
Use animateWithDuration:animations:completion:, and do the "slidey stuff" in the animation block and do the dismissal in the completion block.
[UIView animateWithDuration:.5 animations:^{
//Your custom animation stuff here
} completion:^(BOOL finished) {
[self dismissViewControllerAnimated:YES completion:nil];
}];
Related
I have a view controller(VC1) embedded in a navigation controller(NAV1). In its viewWillAppear method, I make a call to modally present another view controller. In one case I need the new view controller(VC2) to be presented with animation, and in another case it should be presented without animation. VC2 is also embedded in its own navigation controller(NAV2).
All is fine when the animation flag is set to TRUE. When I set the flag to FALSE, couple of things go wrong:
1. I get the following warning in the console: Presenting view controllers on detached view controllers is discouraged
2. When I move back from VC2 after calling dismissViewControllerAnimated:FALSE completion:nil the viewWillAppear method of VC1 does not get called. It gets called if the animation flag is set to TRUE.
In VC1:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff:)
withObject:nil];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
[Utility presentViewController:pNavController
fromViewController:self
animated:FALSE
completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
IN VC2:
[Utility dismissViewController:self
animated:FALSE
completion:nil];
The above method calls the dismissViewControllerAnimated: method.
Not a solution but a workaround:
You can easily postpone any UI operation to the very next event loop by using afterDelay:0:
[self performSelector:#selector(importStuff:)
withObject:nil
afterDelay:0];
This will give a chance to the current operation to complete.
Furthermore, a delay of 0.4 will match the OS. However, whatever delay you use (other than 0) is a kludge and not guaranteed to work under every situation, device and memory load, etc.
Instead, you should revisit your approach.
Change your design:
Do not run the risk of encountering this animation race in the first place.
You have a couple of options, including:
Controlling the transition animation yourself and waiting for its completion prior pushing another view controller (using a completion signal or completion block)
Changing your methodology entirely to avoid this conundrum altogether
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff) withObject:nil afterDelay:0.1];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
SecondViewController *viewMe=[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self.view.window.rootViewController presentViewController:viewMe animated:NO completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
Popping to a controller and calling the reenteringViewController method show below after view will appear ignores the preferredStatusBarUpdateAnimation set in the view controller and instead uses a Slide Animation (which was used previously in the old controller though is set to fade before popping to the current controller).
-(void)reenteringViewController{
[UIView animateWithDuration:0.7 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^
{
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)]) {
[self prefersStatusBarHidden];
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate) withObject:nil];
}
}
completion:^(BOOL finished){
}];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation{
return UIStatusBarAnimationFade;
}
-(BOOL)prefersStatusBarHidden{
return NO;
}
Has anyone experienced this before and if so if they found a solution? I've spent hours pouring over the code but it's very simple and all seems to be calling things correctly but just doesn't use the correct animation. Seems like it could be an iOS7 bug.
I appreciate your help in advance.
When I call dismissViewControllerAnimated:completion: to dismiss a UIViewController the completion block is never executed when the corresponding view is in the middle of being animated onto the screen (using presentViewController:animated:completion:).
The UIViewController does not even dissappear. It is like dismissViewControllerAnimated:completion: is being ignored.
The following code is a simplified code example because the original is much bigger. The code I have given below simulates a use-case where a network communication error might trigger a view to popup whilst another view is also being popped-up at the same time..
Code example:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
}];
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
Log output is:
2013-08-28 16:14:12.162 [1708:c07] Presenting view
2013-08-28 16:14:12.178 [1708:c07] Dismissing view
2013-08-28 16:14:12.583 [1708:c07] View done presenting
Does anyone know how to dismiss the UIViewController in these circumstances?
Thanks in advance.
The reason this code snippet isn't working is because the completion block in these methods are executed at a later time after the animations have completed. You can see this in your logs: "Dismissing view" happens before "View done presenting". Try this instead:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
EDIT:
If you need to make sure the view is dismissed when the network error happens, try setting a boolean instance variable called networkErrorFound.
When you finish the network connection, set this to YES if an error happens. Then use this code:
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
if (self.networkErrorFound) {
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}
}];
That way, it'll wait until it's done presenting to dismiss. You would also need to handle the case that the error happens after the animation is done (for instance, a slow connection that eventually fails), but that's outside the scope of this question.
Why dont you dismiss it when its done loading?
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
OK. It seems like you want to present a VC, but if there is no network found, close the VC. The only reason that I can think of needing to do it this way is if the network only gets checked in the new VC that you are presenting (and want to dismiss if it fails to connect).
And you would be able to achieve that by implementing the code shown in the answer given by #aopsfan.
But think about that UI flow. You are telling a starving man (the user) he can have a sandwich (the next VC that he wants to see)... But WAIT! (dismiss the wanted VC) No, you can't have the sandwich (no network)! Fooled you!.
The way to do it to keep the UI flow nice and not aggravating, would be to check for network connection before presenting the VC. Probably check for network in the IBAction (?) that you use to present the new VC. That way, you can check before presenting. Instead of present-cancel
Heck, you could even show an HUD "in progress" View to let the user know what happening!
I can't find this anywhere. So, if you possess the info about it, please give me a link.
I have one view controller, I made a menu for a simple game. There are some buttons on it.
I have another view controller and there some buttons too.
The question is:
"How I can do an animation of this buttons (hiding off the screen) after I choose one button that triggers a custom segue (without any animation) to another View Controller, which will run it's button animation(coming to the screen from a border of the screen)?"
I made this like this:
1) Theory: I make a IBAction for a menu button, then in this IBAction I call an animation method, which call a performSegueMethod:. After this in new VC in viewWillAppear method call a animation method (that almost equal method from source VC). All this works, but this don't look smooth. The problem with this animation occurs when destination VC replace source VC. There is some split second, when all looks static, and only after this animation starts.
I don't know how to remove this destination VC lag. May be I must load a destination view before a segue? I tried to do this, but may be a made something wrong, or it's just don't help me.
2) Practice:
firstViewController.m:
- (IBAction)newGameSegueButton {
[self menuSlideInDirection:#"CenterToLeft" performingSegueWithIdentifier:#"mode"];
}
-(void)menuSlideInDirection:(NSString *)direction performingSegueWithIdentifier:(NSString *)segueIdentifier
{
[UIView animateWithDuration:0.4 animations:^{
CGPoint newGameButtonCenter;
newGameButtonCenter.x = directionIndex * 160;
newGameButtonCenter.y = self.gameButtonSlide.center.y;
self.gameButtonSlide.center = newGameButtonCenter;
[UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:nil];
[UIView animateWithDuration:0.2 delay:0.2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//animation
} completion:nil];
[UIView animateWithDuration:0.1 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:^(BOOL finished){
if(segueIdentifier){
[self performSegueWithIdentifier:segueIdentifier sender:self];
}
}];
}];
}
Okay, then custom segue code is pretty simple:
-(void)perform
{
UIViewController *src = self.sourceViewController;
UIViewController *dst = self.destinationViewController;
[src.navigationController pushViewController:dst animated:NO];
}
And my secondViewController.m:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil];
}
menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil in secondViewController identical to method with same name in firstView.
I'm looking for smooth animation of particular objects of destination view controller right after a segue.
May be a doing all wrong and there a another way to do this? (I only think of adding all view and all controls of destination VC to source VC and remove "destination VC" at all).
Hope, somebody can help me with this.
Try doing all first view controller's animations in perform method.
As for the second view controller's animations, I don't think there is any other 'good' way than doing it in viewWillAppear (although I would prefer viewDidAppear) since the outlets of the destination view controller are not set while performing the segue(they will be nil). In other words, you do not have a way to access your buttons, let alone animate them.
A hackish way would be to call [segue.destinationViewController view] before perform so that the destination view controller's view hierarchy is loaded and the outlets are set. Then perhaps, you may animate buttons in the destination view controller in perform before pushing it onto navigation stack.
For my question, I choose the way of putting two views under the same VC. Animation is smooth and it's look much better than using perform / viewWillAppear method.
I have a project in which I'm switching one view with another:
- (IBAction)onClick:(id)sender
{
ViewControllerSecond * sc=[[ViewControllerSecond alloc]initWithNibName:#"ViewControllerSecond" bundle:nil];
[UIView transitionFromView:self.view toView:sc.view duration:3.0
options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
}];
}
I'm using 3 seconds here to make a point. in this second view I have a method to update the GUI that adds another view from a view controller:
-(void)updateGUI
{
sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil];
sample.view.frame=CGRectOffset(sample.view.frame, 0, 150);
[self.view addSubview:sample.view];
}
Now, here is the problem: when I'm calling this from the viewDidLoad function - it's working just fine.
However, if called from the viewWillAppear function, the view will appear at the top of the screen and only after the animation has ended will jump to it's position.
How can it be fixed?
Whats wrong putting it in viewDidLoad?
From the looks of it, the order is loading, animating then the delegate methods of appearing. Also put your sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil]; in the init part of your Second view controller.
Alternatively, you can call updateGUI before your animation. So before [UIView transition...
[sc updateGUI];
Just call updateGUI in viewDidLoad. Don't do much work in viewWillAppear. viewWillAppear will prevent your view's appear if you make it do too much work.
The answer for that was of two parts:
update when the view is loaded, however this will only happen once, so to re-use the controller I have to call it again.
use the isViewLoaded to see if the view is loaded. if it is, then call the update method.