I am trying to create a custom transition between to view controllers that are linked to a Container View Controller with custom segues.
This is my attempt at creating the custom transition. However, what happens is the view animates up and down as intended. But the view controllers switch instantly. What I am trying to create is the view sliding off screen vertically, then the container switches view controllers, then it slides back up screen.
Any ideas how I can achieve this animation with a custom animation?
- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
toViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[self transitionFromViewController:fromViewController toViewController:toViewController duration:.4 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.view.frame = CGRectMake(0, 560, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:.4 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
}];
}];
}
[UIViewController transitionFromViewController:toViewController] works only when both fromViewController and toViewController are already the child controllers of the receiver.
The documentation for transitionFromViewController:toViewController: says the following:
Transitions between two of the view controller's child view controllers.
fromViewController: A view controller whose view is currently visible in the parent's view hierarchy.
toViewController: A child view controller whose view is not currently in the view hierarchy.
I didn't test it out however, I think this would work:
- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
CGFloat width = CGRectGetWidth(self.view.frame);
CGFloat height = CGRectGetHeight(self.view.frame);
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
toViewController.view.frame = CGRectMake(0.0, height, width, height);
[self.view addSubView:toViewController.view];
[UIView transitionWithView:self.view
duration:0.4
options:UIViewAnimationOptionCurveLinear
animations:^{
fromViewController.view.frame = CGRectMake(0.0, height, width, height);
toViewController.view.frame = CGRectMake(0.0, 0.0, width, height);
}
completion:^(BOOL finished) {
[fromViewController removeFromParentViewController];
[fromViewController.view removeFromSuperView];
[toViewController didMoveToParentViewController:self];
}];
}
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];
}
I adding view when btw is clicked from left to right and want to remove this view from right to left.
Below is my code while adding view from left to right
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main"
bundle: nil];
leftMenu = (LeftMenuViewController*)[mainStoryboard
instantiateViewControllerWithIdentifier: #"menuController"];
leftMenu.view.frame = CGRectMake(-320, 0, 320-50, self.view.frame.size.height);
[self addChildViewController:leftMenu];
[self.view addSubview:leftMenu.view];
[UIView animateWithDuration:0.3 animations:^{
leftMenu.view.frame = CGRectMake(0, 0, 320-50, self.view.frame.size.height);
hoverView.hidden = NO;
} completion:^(BOOL finished) {
[leftMenu didMoveToParentViewController:self];
}];
For removing this view from right to left what i have tried is:
self.view.frame = CGRectMake(320-50, 0, self.view.frame.size.width, self.view.frame.size.height);
[self willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3 animations:^{
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
[self removeFromParentViewController];
[self.view removeFromSuperview];
}];
I want to remove view from right to left .Any help how to proceed further?
You must move your subview out of frame of superview, so set x-coordinate of your subview to negative value of it's own width. This will cause your view move from right to left out of view.
[self willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3 animations:^{
self.view.frame = CGRectMake(-self.view.frame.size.width, 0, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
[self removeFromParentViewController];
[self.view removeFromSuperview];
}];
You can remove it like this:
CGRect napkinBottomFrame = Yourview.frame;
napkinBottomFrame.origin.x = 0;
[UIView animateWithDuration:0.3 delay:0.0 options: UIViewAnimationOptionCurveEaseOut animations:^{ Yourview.frame = napkinBottomFrame; } completion:^(BOOL finished){/*done*/}];
Swift 3 and 4 version:
leftMenu.didMove(toParentViewController: nil)
UIView.animate(withDuration: 0.3,
animations: {
self.leftMenu.view.frame = CGRect(x: -self.view.frame.width, y: 0, width: self.view.frame.width, height: self.view.frame.height)
},
completion: { finished in
self.leftMenu.view.removeFromSuperview()
self.leftMenu.removeFromParentViewController()
})
P.S. leftMenu should be class variable (property)
Fixed
It turns out the animation was being called twice. I've now fixed it so the animation is only called once and it works perfectly.
I suspect it might be something related to my use of child view controllers, but I'm noticing some strange behaviour when I try to remove the child view controller's view with an animation.
I'm adding the child view controller to the parent, along with the child view controller's view, and animating that view from the bottom of the screen. This works perfectly — my issue is when I try to remove the child view/view controller. I'm animating the child's view to the bottom of the screen, and then calling [self.view removeFromSuperview] in the completion block, but the view is removed immediately (so no animation takes place). If I delete the [self.view removeFromSuperview] line, the animation works correctly, but then the view isn't removed from the parent view controller's view.
Adding the child view controller
(this works as intended)
ChildViewController *myChildViewController = [[ChildViewController alloc] init];
myChildViewController.view.frame = CGRectMake(0.f, self.view.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - 64.f);
[self addChildViewController:myChildViewController];
[self.view addSubview:myChildViewController.view];
[myChildViewController didMoveToParentViewController:self];
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
myChildViewController.view.frame = CGRectMake(0.f, 64.f, self.view.frame.size.width, self.view.frame.size.height - 64.f);
} completion:nil];
Removing the child view controller
(this doesn't work — the view is removed immediately, instead of after completion)
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
self.view.frame = CGRectMake(0.f, self.view.superview.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished)
{
[self willMoveToParentViewController:self.parentViewController];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}];
I've even tried removing [self willMoveToParentViewController:self.parentViewController] and [self removeFromParentViewController] just to see if that changed anything, but as long as [self.view removeFromSuperview] is there, the view disappears immediately.
Add the in the Parent class
- (void) moveToAddJobVC:(NSString*)storyboardId {
ChildViewController *destVC = [self.storyboard instantiateViewControllerWithIdentifier:storyboardId];
destVC.completionHandler = ^(NSString* string) {
NSLog(#"Returned strng.....%#",string);
//[self viewWillAppear:YES];
};
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self addChildViewController:destVC];
[self.view addSubview:destVC.view];
[destVC didMoveToParentViewController:self];
[UIView animateWithDuration:0.3 animations:^{
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}];
}
And in child VC write
#property (nonatomic,copy) void (^completionHandler)(NSString *string);
- (IBAction)dismissBtnAction:(id)sender{
if (self.completionHandler)
{
self.completionHandler(#"Return data");
}
[self removeFromParentViewController];
[self.view removeFromSuperview];
}
I am creating an animation where the current view controller slides down, revealing the new view controller beneath it. I have got this working with the view, but I would like to include the navigation controller in the view sliding down. So instead of a view sliding down with no navigation bar, It would slide down with the navigation bar at the top. How woould I fix this. Here is how I am doing it with the view:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 5.00;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:fromViewController.view];
toViewController.view.frame = CGRectMake(0, 0, 320, 568);
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.frame = CGRectMake(0, 2000, 320, fromViewController.view.frame.size.height);
toViewController.view.frame = CGRectMake(0, 0, 320, fromViewController.view.frame.size.height);
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
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.