I have set up a custom dialog to appear via a custom segue when a button in a container view controller is pressed, the perform function is being called and everything appears to work just fine except for the animation.
Here is the segue on the storyboard:
Here is the button:
Here is the code for the segue:
#import "TimerDialogSegue.h"
#implementation TimerDialogSegue
-(void)perform
{
UIViewController *dst = [self destinationViewController];
//Doesn't appear work with the Timer View Controller as the source
// so I use the parent Home View Controller
UIViewController *src = [self sourceViewController];
src = src.parentViewController;
// set the view frame
CGRect frame;
frame.size.height = src.view.frame.size.height;
frame.size.width = src.view.frame.size.width;
frame.origin.x = src.view.bounds.origin.x;
frame.origin.y = src.view.bounds.origin.y;
dst.view.frame = frame;
// add the view to the hierarchy and bring to front
[src addChildViewController:dst];
[src.view addSubview:dst.view];
[src.view bringSubviewToFront:dst.view];
[UIView animateWithDuration:0.3f
animations:^
{
dst.view.alpha = 1.0f;
}];
}
#end
Here is the code from the Dialog that dismisses it, this part animates just fine
- (IBAction)cancelButtonPressed:(id)sender
{
[self dismiss:sender];
}
- (IBAction)dismiss:(id)sender
{
[UIView animateWithDuration:0.3f
animations:^
{
self.view.alpha = 0.0f;
}
completion:^(BOOL finished)
{
[self.view removeFromSuperview];
[self removeFromParentViewController];
}];
}
To summarize the Dialog appears and operates correctly the only issue is there is no animation when it appears. It does animate when it disappears. Anyone know how I might fix this or a workaround to animate this Dialog when it appears?
Thanks in advance for any help.
You are saying:
[UIView animateWithDuration:0.3f
animations:^
{
dst.view.alpha = 1.0f;
}];
But dst.view.alpha is 1 already so nothing happens. You can only animate a change.
For example, when you dismiss, you animate this:
self.view.alpha = 0.0f;
That works because self.view.alpha was 1 so there is actually a change to animate.
Related
My goal is simple and I have achieved this previously which is why I'm resorting to asking my question here.
I am trying to animate a subview. Simple enough. The view ends up exactly where I want it to be after the animation is over. The only problem is:
whatever I do the animation duration is completely ignored. It happens instantly.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Now I have tried different frames and durations to see if the view was animated at all and it always ends up where I need it to be. Only instantly...
EDIT:
The above code is being called by a delegate in the dismissed view controller like so:
- (void)dismissSelf {
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissScan)]) {
[self.delegate callToDismissScan];
}
}
And that method itself is triggered by a notification.
I must admit that some things are happening during the transition between the two controllers that I don't fully understand. (And I would very much like to understand them...)
You can try
[self.view layoutIfNeeded];
like this
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Maybe you need try like this.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
[UIView animateWithDuration:0.3 delay:0.3
options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
view.frame = frame;
}
completion:^(BOOL finished){
[view removeFromSuperview];
//[self dismissViewControllerAnimated:NO completion:nil];//uncomment if need.
}];
}
It seems there was no way in hell I could animate the transition in the presenting view controller and so the simplest way to resolve the issue was to animate the transition in the presented view controller before it is being dismissed.
The way I did this was by casting the delegate to a view controller. It was then easy to add it's view as a sub-view of my presented view controller and animate it like so:
- (void)dismissSelf {
UIView *dummyView = [ScreenCapper captureScreenIn: self];
UIViewController *presentingViewController = (UIViewController *)self.delegate;
[self.view.window addSubview: presentingViewController.view];
[self.view.window addSubview: dummyView];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
dummyView.frame = CGRectMake(dummyView.frame.size.width, dummyView.frame.origin.y, dummyView.frame.size.width, dummyView.frame.size.height);
} completion:^(BOOL finished){
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissView)]) {
[self.delegate callToDismissView];
}
}];
}
And in the presenting view controller all that was left to do was to dismiss the presented view controller without animations.
- (void)callToDismissView {
[self dismissViewControllerAnimated:NO completion:nil];
}
With the following code I'm showing a view with an animation on a button click.
UIViewController *modalView = self.pageViewController;
[self.view addSubview:modalView.view];
[UIView animateWithDuration:1 animations:^{
CGRect currentRect = modalView.view.frame;
currentRect.origin.y = 650.0f;
currentRect.size.height = 295.0f;
[modalView.view setFrame:currentRect];
[modalView.view removeFromSuperview];
}];
[self.view addSubview:_pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
This works fine for first time button click. When I'm pressing the button again and again this is not animating anymore. It's animating for the first time only. Is there any way to do so?
Thanks in Advance!
The cause is that modalView.view's frame initially if different from
currentRect.origin.y = 650.0f;
currentRect.size.height = 295.0f;
so when changes its frame animation is happens.
But next time when you perform button's action modalView.view's current frame and that frame you sets are same. So there is nothing to animate at all.
Also, you should move [modalView.view removeFromSuperview]; from animation block to completion block
[UIView animateWithDuration:1 animations:^{
CGRect currentRect = modalView.view.frame;
currentRect.origin.y = 650.0f;
currentRect.size.height = 295.0f;
[modalView.view setFrame:currentRect];}
completion:^(BOOL finished) {
[modalView.view removeFromSuperview];
}];
I have a single view App and want to show a new ViewController when pressing a nav bar button in the right hand side. I call this VC by this code:
- (IBAction)createEntryButton:(id)sender {
CreateEntryViewController *vc2 = [[CreateEntryViewController alloc] init];
[self presentViewController:vc2 animated:TRUE completion:nil];
}
This animation, however, brings the vc2 in from the bottom which seems counter-intuitive according to my UI. So my question is:
How can I make my vc2 appear from the right instead of the bottom with presentViewController?
Thanks.
the cleanest would be to use a navigationController for pushing and popping views..
if you are already in a NavigationController
[self.navigationCtroller pushViewController:vc2 animated:TRUE completion:nil]
if you aren't, adapt the code where your view controller is added to the window. If your VC is the rootWindowController and you are not using storyboarding, this is likely in your AppDelegate
if you use storyboards, adapt the storyboard so you are inside a navigation controller
ELSE if you don't want that for any reason: :) just manually animate in the 2. VC's view using [UIView animate:vc2.view ....]
written inline -- method names don't match but shows general approach:
UIView *v = vc2.view;
CGRect f = v.frame;
f.origin.x += self.view.frame.size.width; //move to right
v.frame = f;
[UIView animateWithDuration:0.5 animations:^{
v.frame = self.view.frame;
} completion:^(BOOL finished) {
[self presentViewController:vc2 animated:NO completion:nil];
}];
in the completion block present the view controller vc2 non-animated as you already did that yourself
This helped me,
- (void)presentNewViewController{
NewViewController *objNewViewController =[[NewViewController alloc]initWithNibName:#"NewViewController" bundle:nil];
UIView *tempNewVCView = [UIView new];
tempNewVCView = objNewViewController.view;
tempNewVCView.frame = self.view.frame;
CGRect initialFrame = self.view.frame;
initialFrame.origin.x = self.view.frame.size.width;
tempNewVCView.frame = initialFrame;
[self.view addSubview:tempNewVCView];
[UIView animateWithDuration:0.3 animations:^{
tempNewVCView.frame = self.view.frame;
} completion:^(BOOL finished) {
[self presentViewController:objNewViewController animated:NO completion:^{
}];
}];
}
This is both a question and a partial solution.
*Sample project here:
https://github.com/JosephLin/TransitionTest
Problem 1:
When using transitionFromViewController:..., layouts done by the toViewController's viewWillAppear: doesn't show up when the transition animation begins. In other words, the pre-layout view shows during the animation, and it's contents snap to the post-layout positions after the animation.
Problem 2:
If I customize the background of my navbar's UIBarButtonItem, the bar button shows up with the wrong size/position before the animation, and snaps to the correct size/position when the animation ends, similar to Problem 1.
To demonstrate the problem, I made a bare-bone custom container controller that does some custom view transitions. It's pretty much a UINavigationController copy that does cross-dissolve instead of push animation between views.
The 'Push' method looks like this:
- (void)pushController:(UIViewController *)toViewController
{
UIViewController *fromViewController = [self.childViewControllers lastObject];
[self addChildViewController:toViewController];
toViewController.view.frame = self.view.bounds;
NSLog(#"Before transitionFromViewController:");
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{}
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
}];
}
Now, DetailViewController (the view controller I'm pushing to) needs to layout its content in viewWillAppear:. It can't do it in viewDidLoad because it wouldn't have the correct frame at that time.
For demonstration purpose, DetailViewController sets its label to different locations and colors in viewDidLoad, viewWillAppear, and viewDidAppear:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%s", __PRETTY_FUNCTION__);
CGRect rect = self.descriptionLabel.frame;
rect.origin.y = 50;
self.descriptionLabel.frame = rect;
self.descriptionLabel.text = #"viewDidLoad";
self.descriptionLabel.backgroundColor = [UIColor redColor];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"%s", __PRETTY_FUNCTION__);
CGRect rect = self.descriptionLabel.frame;
rect.origin.y = 200;
self.descriptionLabel.frame = rect;
self.descriptionLabel.text = #"viewWillAppear";
self.descriptionLabel.backgroundColor = [UIColor yellowColor];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"%s", __PRETTY_FUNCTION__);
CGRect rect = self.descriptionLabel.frame;
rect.origin.y = 350;
self.descriptionLabel.frame = rect;
self.descriptionLabel.text = #"viewDidAppear";
self.descriptionLabel.backgroundColor = [UIColor greenColor];
}
Now, when pushing the DetailViewController, I'm expecting to see the label at y =200 at the begining of the animation (left image), and then jumps to y = 350 after the animation is finished (right image).
Expected view before and after animation.
However, the label was at y=50, as if the layout made in viewWillAppear didn't make it before the animation took place (left image). But notice that the label's background was set to yellow (the color specified by viewWillAppear)!
Wrong layout at the beginning of the animation. Notice that the bar buttons also start with the wrong position/size.
Console Log
TransitionTest[49795:c07] -[DetailViewController viewDidLoad]
TransitionTest[49795:c07] Before transitionFromViewController:
TransitionTest[49795:c07] -[DetailViewController viewWillAppear:]
TransitionTest[49795:c07] -[DetailViewController viewWillLayoutSubviews]
TransitionTest[49795:c07] -[DetailViewController viewDidLayoutSubviews]
TransitionTest[49795:c07] -[DetailViewController viewDidAppear:]
Notice that viewWillAppear: was called AFTER transitionFromViewController:
Solution for Problem 1
Alright, here comes the partial solution part. By explicitly calling beginAppearanceTransition: and endAppearanceTransition to toViewController, the view will have the correct layout before the transition animation takes place:
- (void)pushController:(UIViewController *)toViewController
{
UIViewController *fromViewController = [self.childViewControllers lastObject];
[self addChildViewController:toViewController];
toViewController.view.frame = self.view.bounds;
[toViewController beginAppearanceTransition:YES animated:NO];
NSLog(#"Before transitionFromViewController:");
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{}
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
[toViewController endAppearanceTransition];
}];
}
Notice that viewWillAppear: is now called BEFORE transitionFromViewController:
TransitionTest[18398:c07] -[DetailViewController viewDidLoad]
TransitionTest[18398:c07] -[DetailViewController viewWillAppear:]
TransitionTest[18398:c07] Before transitionFromViewController:
TransitionTest[18398:c07] -[DetailViewController viewWillLayoutSubviews]
TransitionTest[18398:c07] -[DetailViewController viewDidLayoutSubviews]
TransitionTest[18398:c07] -[DetailViewController viewDidAppear:]
But that doesn't fix Problem 2!
For whatever reason, the navbar buttons still begin with the wrong position/size at the beginning of the transition animation. I spent so many time trying to find THE right solution but without luck. I'm starting to feel it's a bug in transitionFromViewController: or UIAppearance or whatever. Please, any insight you can offer to this question is greatly appreciated. Thanks!
Other solutions I've tried
Call [self.view addSubview:toViewController.view]; before transitionFromViewController:
It actually gives exactly the right result to the user, fixes both Problem 1&2. The problem is, viewWillAppear and viewDidAppear will both be called twice! It's problematic if I want to do some expansive animation or calculation in viewDidAppear.
Call [toViewController viewWillAppear:YES]; before transitionFromViewController:
I think it's pretty much the same as calling beginAppearanceTransition:. It fixes Problem 1 but not Problem 2. Plus, the doc says not to call viewWillAppear directly!
Use [UIView animateWithDuration:] instead of transitionFromViewController:
Like this:
[self addChildViewController:toViewController];
[self.view addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:0.5 animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
}];
It fixes Problem 2, but the view started with the layout in viewDidAppear (label is green, y=350). Also, the cross-dissolve is not as good as using UIViewAnimationOptionTransitionCrossDissolve
Ok, adding layoutIfNeeded to the toViewController.view seems to do the trick - this gets the view laid out properly before it shows up on screen (without the add/remove), and no more weird double viewDidAppear: call.
- (void)pushController:(UIViewController *)toViewController
{
UIViewController *fromViewController = [self.childViewControllers lastObject];
[self addChildViewController:toViewController];
toViewController.view.frame = self.view.bounds;
[toViewController.view layoutIfNeeded];
NSLog(#"Before transitionFromViewController:");
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{}
completion:^(BOOL finished) {
}];
}
Had the same problem, all you need is forwarding appearance transactions and UIView.Animate. This approach fixes all problems, doesn't create new ones. Here is some C# code (xamarin):
var fromView = fromViewController.View;
var toView = toViewController.View;
fromViewController.WillMoveToParentViewController(null);
AddChildViewController(toViewController);
fromViewController.BeginAppearanceTransition(false, true);
toViewController.BeginAppearanceTransition(true, true);
var frame = fromView.Frame;
frame.X = -viewWidth * direction;
toView.Frame = frame;
View.Add(toView);
UIView.Animate(0.3f,
animation: () =>
{
toView.Frame = fromView.Frame;
fromView.MoveTo(x: viewWidth * direction);
},
completion: () =>
{
fromView.RemoveFromSuperview();
fromViewController.EndAppearanceTransition();
toViewController.EndAppearanceTransition();
fromViewController.RemoveFromParentViewController();
toViewController.DidMoveToParentViewController(this);
}
);
and of course you should disable automatic forwarding of appearance methods and do it manually:
public override bool ShouldAutomaticallyForwardAppearanceMethods
{
get { return false; }
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
CurrentViewController.BeginAppearanceTransition(true, animated);
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
CurrentViewController.EndAppearanceTransition();
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
CurrentViewController.BeginAppearanceTransition(false, animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
CurrentViewController.EndAppearanceTransition();
}
I'm try to popViewcontroller with transform scale animation
according this code when it begin transform it present the black screen instead of the Parent View
how to fix this ?
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
self.view.alpha = 1.0f;
self.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
} completion:^(BOOL finished) {
[[self navigationController] popViewControllerAnimated:NO];
}];
This is the expected behavior, because the view of the previous controller is not in the view hierarchy until popViewControllerAnimated: method is called and you call it after the animation finishes.
I don't think adding subviews directly to the view of the navigation controller is a good idea, but the following code should work for you.
UINavigationController* navigationController;
CGRect frame;
//keep a reference to the navigation controller as
//[self navigationController] won't work after pop is called
navigationController = [self navigationController];
//remember the frame of the view relative to navigation controller's view
frame = [navigationController.view convertRect:self.view.frame fromView:self.view.superview];
//pop this controller, this will add the view of the
//previous controller into the view hierarchy
[navigationController popViewControllerAnimated:NO];
self.view.frame = frame;
//add this view on top of the previous one
[navigationController.view addSubview:self.view];
[UIView animateWithDuration:0.5f
delay:0.0f
options:0
animations:^{
self.view.alpha = 0.0f;
self.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
}];
By the way, UIViewAnimationCurveEaseInOut is not a correct constant for the options parameter. You should use the constants that start with UIViewAnimationOption for this method.