I subclassed UIStoryboardSegue to perform custom segue animations. To make the animation visible I have to add the view from the destination ViewController as a temp view. After performing the animations I am removing the temp view and presenting the final ViewController via (presentViewController:animated:NO).
This however leads to the following warning: "Unbalanced calls to begin/end appearance transitions for".
If I keep my temp-view and do not remove it the warning does not appear but the final view does not respond to touch events anymore. How to get rid of this warning?
My custom segue code:
-(void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
destinationViewController.view.alpha = 0.;
destinationViewController.view.transform = CGAffineTransformScale(destinationViewController.view.transform, 4.0, 4.0);
[sourceViewController.view addSubview:destinationViewController.view];
[UIView animateWithDuration:kSSSpriteKitFadeOutDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
destinationViewController.view.transform = CGAffineTransformScale(destinationViewController.view.transform, 0.25, 0.25);
destinationViewController.view.alpha = 1.;
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview]; // remove from temp super view
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}
I am taking 4 UIStoryboardSegue type class. 2 for animation start and rest 2 for animation stop. So i am going to share all code for 4 classes. please try to solve this.
CustomSegue.h
#import <UIKit/UIKit.h>
#interface CustomSegue : UIStoryboardSegue
// Originating point for animation
#property CGPoint originatingPoint;
#property CGRect Startingframe;
#end
CustomSegue.m
#import "CustomSegue.h"
#implementation CustomSegue
- (void)perform
{
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
// Add the destination view as a subview, temporarily
[sourceViewController.view addSubview:destinationViewController.view];
// Transformation start scale
float width = 320;
float height = 480;
float x_Scale = self.Startingframe.size.width/width;
float Y_Scale = self.Startingframe.size.height/height;
destinationViewController.view.transform = CGAffineTransformMakeScale(x_Scale, Y_Scale);
// Store original centre point of the destination view
CGPoint originalCenter = destinationViewController.view.center;
// Set center to start point of the button
destinationViewController.view.center = self.originatingPoint;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^
{
// Grow!
destinationViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
destinationViewController.view.center = originalCenter;
}
completion:^(BOOL finished)
{
[destinationViewController.view removeFromSuperview]; // remove from temp super view
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}
#end
CustomUnwindSegue.h
#import <UIKit/UIKit.h>
#interface CustomUnwindSegue : UIStoryboardSegue
// Target point to animate to (on the button)
#property CGPoint targetPoint;
#property CGRect endFrame;
#end
CustomUnwindSegue.m
#import "CustomUnwindSegue.h"
#implementation CustomUnwindSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
// Transformation End scale
float width = 320;
float height = 480;
float x_Scale = self.endFrame.size.width/width;
float Y_Scale = self.endFrame.size.height/height;
// Add view to super view temporarily
[sourceViewController.view.superview insertSubview:destinationViewController.view atIndex:0];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// Shrink!
sourceViewController.view.transform = CGAffineTransformMakeScale(x_Scale, Y_Scale);
sourceViewController.view.center = self.targetPoint;
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview]; // remove from temp super view
[sourceViewController dismissViewControllerAnimated:NO completion:NULL]; // dismiss VC
}];
}
#end
Just connect your action with this custom segue animation like below:
Related
I've got a custom UIStoryboardSegue subclass which just replaces the root view controller with the destination VC. Works exactly as I want it to... however, I'd like to be able to add a transition animation, and I can't find any good examples of how to do that in the context of replacing the root VC.
The -perform selector of my class is this:
-(void)perform {
UIViewController *source = (UIViewController *)self.sourceViewController;
source.view.window.rootViewController = self.destinationViewController;
}
...How do I add a nice animated transition?
There are numerous ways you could add a "nice animation". Here is an example of a sort of card shuffle animation where one view moves up and left, while the other moves down and right, then reverses after changing the z-order of the two views. This implementation inserts the destination controller's view into the window's view hierarchy under the source controller's view.
-(void)perform {
CGFloat dur = 1.0;
UIView *destView = [self.destinationViewController view];
UIView *srcView = [self.sourceViewController view];
CGFloat viewWidth = srcView.frame.size.width;
CGPoint center = srcView.center;
AppDelegate *appDel = [[UIApplication sharedApplication] delegate];
destView.frame = srcView.bounds;
[appDel.window insertSubview:destView belowSubview:srcView];
[UIView animateWithDuration:dur animations:^{
srcView.frame = CGRectOffset(srcView.frame, -viewWidth/1.9, -20);
destView.frame = CGRectOffset(destView.frame, viewWidth/1.9, 20);
} completion:^(BOOL finished) {
[appDel.window bringSubviewToFront:destView];
[UIView animateWithDuration:dur animations:^{
destView.center = center;
srcView.center = center;
} completion:^(BOOL finished) {
[srcView removeFromSuperview];
appDel.window.rootViewController = self.destinationViewController;
}];
}];
}
I am trying to create a push segue animation where the sourceVC drops down off screen vertically and the destinationVC appears upwards onto the screen vertically. My custom segue seems to almost make this animation, however, upon the destinationVC appearing the animation does not work. Here is my -perform segue.
-(void)perform
{
UIViewController *sourceVC = self.sourceViewController;
UIViewController *destVC = self.destinationViewController;
[sourceVC.view addSubview:destVC.view];
float width = sourceVC.view.frame.size.width;
float height = sourceVC.view.frame.size.height;
destVC.view.frame = CGRectMake(0, 560, width, height);
[UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear
animations:^{
[destVC.view removeFromSuperview];
sourceVC.view.frame = CGRectMake(0, 560, width, height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
destVC.view.frame = CGRectMake(0, 0, destVC.view.frame.size.width, destVC.view.frame.size.height);
} completion:^(BOOL finished) {
[sourceVC.navigationController pushViewController:destVC animated:NO];
}];
}];
}
The second animation is the one that is not working.
Any ideas what is wrong with my code? Thanks for any help.
I needed to create a custom Animator class that conformed to UIViewControllerAnimatedTransitioning.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface SAAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
I then implemented the required methods.
#implementation SAAnimator
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.2;
}
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
fromViewController.view.userInteractionEnabled = NO;
[[transitionContext containerView]addSubview:fromViewController.view];
[[transitionContext containerView]addSubview:toViewController.view];
toViewController.view.frame = CGRectMake(0, 560, toViewController.view.frame.size.width, toViewController.view.frame.size.height);
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
fromViewController.view.frame = CGRectMake(0, 560, fromViewController.view.frame.size.width, fromViewController.view.frame.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
toViewController.view.frame = CGRectMake(0, 0, toViewController.view.frame.size.width, toViewController.view.frame.size.height);
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}];
}
#end
Then very simply, in my view controller performing the push segue, I made the class conform to the UINavigationControllerDelegate and implement this method.
#pragma mark - Animated Transiton
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
SAAnimator * animator = [SAAnimator new];
return animator;
}
return nil;
}
In prepareForSegue set the navigation controller's delegate
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toCreateVC"]) {
self.navigationController.delegate = self;
}
}
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.
I'm using a custom segue to create an expand animation when I push new view controllers onto my navigation stack. If no sourceRect is specified when prepareForSegue:sender: is called, then the destination view controller is expanded from the center of the source view controller.
ExpandSegue.m:
- (id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
self = [super initWithIdentifier:identifier source:source destination:destination];
self.sourceRect = CGRectMake(source.view.center.x, source.view.center.y, 0.0, 0.0);
return self;
}
- (void)perform {
UIViewController *sourceViewController = (UIViewController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *)self.destinationViewController;
CGRect destinationRect = sourceViewController.view.frame;
CGFloat tx = self.sourceRect.origin.x + self.sourceRect.size.width/2 - destinationRect.origin.x - destinationRect.size.width/2;
CGFloat ty = self.sourceRect.origin.y + self.sourceRect.size.height/2 - destinationRect.origin.y - destinationRect.size.height/2;
CGFloat sx = self.sourceRect.size.width/destinationRect.size.width;
CGFloat sy = self.sourceRect.size.height/destinationRect.size.height;
[sourceViewController.view addSubview:destinationViewController.view];
[destinationViewController.view setFrame:sourceViewController.view.frame];
[destinationViewController.view setTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(sx, sy), CGAffineTransformMakeTranslation(tx, ty))];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^{
[destinationViewController.view setTransform:CGAffineTransformIdentity];
} completion:^(BOOL finished) {
[destinationViewController.view removeFromSuperview];
[sourceViewController.navigationController pushViewController:destinationViewController animated:NO];
}];
}
The expand segue works as expected. I tried to create a collapse segue by reversing the logic from the expand segue, but this has given me problems. I want the source view controller to shrink down to the size of the destinationRect. If no destinationRect is supplied, then I want the view to shrink to the center of the destination view controller's view.
CollapseSegue.m:
- (id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
self = [super initWithIdentifier:identifier source:source destination:destination];
self.destinationRect = CGRectMake(source.view.center.x, source.view.center.y, 0.0, 0.0);
return self;
}
- (void)perform {
UIViewController *sourceViewController = (UIViewController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *)self.destinationViewController;
CGRect sourceRect = sourceViewController.view.frame;
CGFloat tx = self.destinationRect.origin.x + self.destinationRect.size.width/2 - sourceRect.origin.x - sourceRect.size.width/2;
CGFloat ty = self.destinationRect.origin.y + self.destinationRect.size.height/2 - sourceRect.origin.y - sourceRect.size.height/2;
CGFloat sx = self.destinationRect.size.width/sourceRect.size.width;
CGFloat sy = self.destinationRect.size.height/sourceRect.size.height;
[sourceViewController.navigationController popViewControllerAnimated:NO];
[destinationViewController.view addSubview:sourceViewController.view];
[sourceViewController.view setFrame:destinationViewController.view.frame];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^{
[sourceViewController.view setTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(sx, sy), CGAffineTransformMakeTranslation(tx, ty))];
} completion:^(BOOL finished) {
[sourceViewController.view removeFromSuperview];
}];
}
Rather than animating nicely, the view for the sourceViewController simply disappears as if I had only called [sourceViewController.navigationController popViewControllerAnimated:NO]; This would seem to indicate that I'm not setting up my view hierarchy properly, but I can't seem to figure out where I'm going wrong! Any help would be greatly appreciated.
I'd like to replicate the behavior of the iPhone's Music app. When you're playing an album in that app and you tap the upper right button the album cover flips around to show a UITableView of tracks behind it.
Is it possible to accomplish this with a custom UIStoryboardSegue?
Or is the best way just to flip between two views that use the same controller?
It is probably simpler to flip between two views of the same view controller, e.g.
- (IBAction)showTracksView
{
[UIView transitionWithView:self.view
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ tracksView.hidden = NO; }
completion:^(BOOL finished){ self.navigationItem.title = #"Tracks"; }];
}
- (IBAction)hideTracksView
{
[UIView transitionWithView:self.view
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ tracksView.hidden = YES; }
completion:^(BOOL finished){ self.navigationItem.title = #"Album cover"; }];
}
where tracksView is your UITableView of tracks.
I had this challenge and solved it using a custom segue to present the view controller. Just create a new class based on UIStoryboardSegue.
Here is my custom segue
.h file:
#import <UIKit/UIKit.h>
#interface BRTrackNotesSegue : UIStoryboardSegue
#end
.m file
#implementation BRTrackNotesSegue
- (void) perform {
UIViewController *src = (UIViewController *) self.sourceViewController;
UIViewController *dst = (UIViewController *) self.destinationViewController;
[UIView transitionWithView:src.navigationController.view duration:0.50
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[src.navigationController pushViewController:dst animated:NO];
}
completion:NULL];
}
#end
In the interface builder select the segue and set the Segue Class to the nam of your custom segue.
The second view controller contains the following to dismiss with the same animation :
- (IBAction)done:(id)sender {
[UIView transitionWithView:self.navigationController.view
duration:0.50
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:nil];
[self.navigationController popViewControllerAnimated:NO];
}