Based on the Apple documentation I came up with the following method to switch between controllers in a containment controller. When there is an oldC I am getting Unbalanced calls to begin/end appearance transitions for <...> on the console.
- (void) showController:(UIViewController*)newC withView:(UIView*)contentView animated:(BOOL)animated
{
UIViewController *oldC = self.childViewControllers.firstObject;
if (oldC == newC) {
return;
}
[oldC willMoveToParentViewController:nil];
[self addChildViewController:newC];
newC.view.frame = (CGRect){ 0, 0, contentView.frame.size };
[contentView addSubview:newC.view];
if (animated && oldC != nil) {
oldC.view.alpha = 1.0f;
newC.view.alpha = 0.0f;
[self transitionFromViewController:oldC toViewController:newC duration:0.25f options:0 animations:^{
oldC.view.alpha = 0.0f;
newC.view.alpha = 1.0f;
} completion:^(BOOL finished) {
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}];
} else {
oldC.view.alpha = 0.0f;
newC.view.alpha = 1.0f;
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}
}
This is how I call it:
- (IBAction) buttonSignIn:(id)sender
{
[self showController:self.signInViewController withView:self.contentView animated:(sender != nil)];
}
- (IBAction) buttonSignUp:(id)sender
{
[self showController:self.signUpViewController withView:self.contentView animated:(sender != nil)];
}
To track this down I am logging the appearance transitions
-(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated
{
[super beginAppearanceTransition:isAppearing animated:animated];
NSLog(#"**begin %#", self);
}
-(void)endAppearanceTransition
{
[super endAppearanceTransition];
NSLog(#"**end** %#", self);
}
This is what the log looks like:
] **begin <SignInViewController: 0x10c769a20>
] **begin <SignUpViewController: 0x10c768770>
] Unbalanced calls to begin/end appearance transitions for <SignUpViewController: 0x10c768770>.
] **end** <SignUpViewController: 0x10c768770>
] **end** <SignInViewController: 0x10c769a20>
Now I am a little puzzled. What's the problem here?
Turns out transitionFromViewController:toViewController:duration:options:animations:completion: also adds the view.
This method adds the second view controller’s view to the view hierarchy and then performs the animations defined in your animations block. After the animation completes, it removes the first view controller’s view from the view hierarchy.
Which means the addSubview needs to be adjusted accordingly.
- (void) showController:(UIViewController*)newC withView:(UIView*)contentView animated:(BOOL)animated
{
UIViewController *oldC = self.childViewControllers.firstObject;
if (oldC == newC) {
return;
}
[oldC willMoveToParentViewController:nil];
[self addChildViewController:newC];
newC.view.frame = (CGRect){ 0, 0, contentView.frame.size };
if (animated && oldC != nil) {
oldC.view.alpha = 1.0f;
newC.view.alpha = 0.0f;
[self transitionFromViewController:oldC toViewController:newC duration:0.25f options:0 animations:^{
oldC.view.alpha = 0.0f;
newC.view.alpha = 1.0f;
} completion:^(BOOL finished) {
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}];
} else {
[contentView addSubview:newC.view];
oldC.view.alpha = 0.0f;
newC.view.alpha = 1.0f;
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}
}
old code
[self addChildViewController:toVC];
[fromVC willMoveToParentViewController:nil];
[self.view addSubview:toVC.view];
[toVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
[self transitionFromViewController:fromVC
toViewController:toVC duration:0
options:UIViewAnimationOptionTransitionNone
animations:^{
} completion:^(BOOL finished) {
[fromVC.view removeFromSuperview];
[fromVC removeFromParentViewController];
[toVC didMoveToParentViewController:self];
self.currentVC = toVC;
}];
new code, it's ok
[self addChildViewController:toVC];
[fromVC willMoveToParentViewController:nil];
[self transitionFromViewController:fromVC
toViewController:toVC duration:0
options:UIViewAnimationOptionTransitionNone
animations:^{
} completion:^(BOOL finished) {
[fromVC.view removeFromSuperview];
[fromVC removeFromParentViewController];
[toVC didMoveToParentViewController:self];
self.currentVC = toVC;
}];
I set the duration to 0, which means animation:NO for the transitionFromViewControllercall.
Related
I want to do continuous animation in 3 imageviews back to back I have use the below code but it shows me animation in only first imageview.I debug Code, it will Go in method but it won't show Any animation in remaining.Below is my Code
-(void)imageNewsAnimation
{
[UIView animateWithDuration:0.35f animations:^{
_imgNews.animationImages=arrnews;
_imgNews.animationDuration=1;
_imgNews.animationRepeatCount=1;
_imgNews.image = [_imgNews.animationImages lastObject];
[_imgNews startAnimating];
} completion:^(BOOL finished) {
[_imgNews stopAnimating];
[self imageEventAnimation];
} ];
}
-(void)imageEventAnimation
{
[UIView animateWithDuration:0.35f delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
_imgEvents.animationImages=arrEvents;
_imgEvents.animationDuration=1;
_imgEvents.animationRepeatCount=1;
_imgEvents.image = [_imgEvents.animationImages lastObject];
[_imgEvents startAnimating];
} completion:^(BOOL finished) {
[self imageDirectoryAnimation];
} ];
}
-(void)imageDirectoryAnimation
{
[UIView animateWithDuration:0.35f animations:^{
_imgDirectory.animationImages=arrdir;
_imgDirectory.animationDuration=1;
_imgDirectory.animationRepeatCount=1;
_imgDirectory.image = [_imgDirectory.animationImages lastObject];
[_imgDirectory startAnimating];
} completion:^(BOOL finished) {
[self imageInfoAnimation];
} ];
}
-(void)imageInfoAnimation
{
[UIView animateWithDuration:0.35f animations:^{
_imgInfo.animationImages=arrinfo;
_imgInfo.animationDuration=1;
_imgInfo.animationRepeatCount=1;
_imgInfo.image = [_imgInfo.animationImages lastObject];
[_imgInfo startAnimating];
} completion:^(BOOL finished) {
[self imageDirectoryAnimation];
} ];
}
Since startAnimating doesn't have a completion handler, I would set up something like this:
CycleImageView.h
#import <UIKit/UIKit.h>
typedef void (^CycleCompletion)(BOOL finished);
#interface CycleImageView : UIImageView
- (void)startAnimatingWithImages:(NSArray<UIImage *>*)images completion:(CycleCompletion)completion;
#end
CycleImageView.m
#import "CycleImageView.h"
#interface CycleImageView()
#property (nonatomic, copy) CycleCompletion completion;
#end
#implementation CycleImageView
- (void)startAnimatingWithImages:(NSArray<UIImage *>*)images completion:(CycleCompletion)completion {
self.completion = completion;
NSMutableArray *imageRefs = [NSMutableArray array];
for (NSInteger i = 0; i < images.count; i++) {
[imageRefs addObject:(id)images[i].CGImage];
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.delegate = self;
animation.calculationMode = kCAAnimationDiscrete;
animation.duration = 1;
animation.values = imageRefs;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.layer addAnimation:animation forKey:#"animation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (self.completion != nil) {
self.completion(flag);
self.completion = nil;
}
}
#end
To use it:
Make imgNews, imgEvents, imgDirectory, and imgInfo all subclasses of CycleImageView. Then, using your supplied methods and variables, the implementation would change to this:
- (void)imageNewsAnimation {
[_imgNews startAnimatingWithImages:arrnews completion:^(BOOL finished) {
if (finished) {
[self imageEventAnimation];
}
}];
}
- (void)imageEventAnimation {
[_imgEvent startAnimatingWithImages:arrEvents completion:^(BOOL finished) {
if (finished) {
[self imageDirectoryAnimation];
}
}];
}
- (void)imageDirectoryAnimation {
[_imgDirectory startAnimatingWithImages:arrdir completion:^(BOOL finished) {
if (finished) {
[self imageInfoAnimation];
}
}];
}
- (void)imageInfoAnimation {
[_imgInfo startAnimatingWithImages:arrinfo completion:^(BOOL finished) {
if (finished) {
// NOTE: Are you intentionally creating this indefinite animation loop?
[self imageDirectoryAnimation];
}
}];
}
Edit:
Created a gist for CycleImageView, as well.
Perform animation with timer like
In viewDidLoad:
timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(imageAnimation:) userInfo:nil repeats:YES];
i = 0;
then
- (void)imageAnimation:(UIImage *) image {
i++;
if (i > 2) {
i = 0;
}
NSArray *arr = #[[UIImage imageNamed:#"img1"],[UIImage imageNamed:#"img2"],[UIImage imageNamed:#"img3"]];
UIImage * toImage = [arr objectAtIndex:i];
[UIView transitionWithView:self.view
duration:2
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.imageview.image = toImage;
} completion:NULL];
}
Hope this help!
I am popping the couponDetailsView with bounce animation. But i want to dismiss the view from left to right animation. How can i do this? Below is my source code. Any kind of help would be really helpful.
#pragma mark Bounce Animation
-(void) openContentDetailsView
{
[self.view bringSubviewToFront:self.couponDetailsView];
[UIView animateWithDuration:0.2 animations:
^(void){
self.couponDetailsView.transform = CGAffineTransformScale(CGAffineTransformIdentity,1.1f, 1.1f);
self.couponDetailsView.alpha = 0.5;
}
completion:^(BOOL finished){
[self bounceOutAnimationStoped];
}];
}
- (void)bounceOutAnimationStoped
{
[UIView animateWithDuration:0.1 animations:
^(void){
self.couponDetailsView.transform = CGAffineTransformScale(CGAffineTransformIdentity,0.9, 0.9);
self.couponDetailsView.alpha = 0.8;
}
completion:^(BOOL finished){
[self bounceInAnimationStoped];
}];
}
- (void)bounceInAnimationStoped
{
[UIView animateWithDuration:0.1 animations:
^(void){
self.couponDetailsView.transform = CGAffineTransformScale(CGAffineTransformIdentity,1, 1);
self.couponDetailsView.alpha = 1.0;
}
completion:^(BOOL finished){
[self animationStoped];
}];
}
- (void)animationStoped
{
}
- (IBAction)contentDetailsCloseButtonAction:(id)sender {
self.couponDetailsView.alpha = 0;
self.couponDetailsView.transform = CGAffineTransformScale(CGAffineTransformIdentity,0.6, 0.6);
}
Try this:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
animations:^{
[testView setFrame:CGRectMake(x, y, width, height)];
} completion:^(BOOL finished) {
[testView removeFromSuperview];
}];
I have a tableview with a hidden property. I want to unhide it with animation so that it could slide from bottom to top? Any suggestions please.
-(IBAction) hideTable : (id) sender;
{
if(self.tb.hidden==true)
{
self.tb.alpha=1;
[self.tb setHidden:NO];
}
else{
[self.tb setHidden:YES];
}
}
-(IBAction) hideTable : (id) sender{
if (self.sessionView.hidden == YES) {
// Currently NOT Visible. Show it now
// First unhide it
yourTableView.hidden = NO;
[UIView animateWithDuration:0.5f
animations:^{
// Get the Existing table View frame
CGRect hiddenFrame = yourTableView.frame;
// Reset it to 0 or your own value
hiddenFrame.origin.y = 0;
// Set the new frame
yourTableView.frame = hiddenFrame;
} completion:^(BOOL finished) {
NSLog(#"Shown");
}];
}
else{
// Currently Visible. Hide it Now
[UIView animateWithDuration:0.5f
animations:^{
// Get the Existing table View frame
CGRect hiddenFrame = yourTableView.frame;
// Under the superview height
hiddenFrame.origin.y = yourTableView.superview.frame.size.height;
// Set the new frame
yourTableView.frame = hiddenFrame;
} completion:^(BOOL finished) {
yourTableView.hidden = YES;
NSLog(#"Hidden");
}];
}
}
try this
CGRect frame = self.tb.frame;
if (self.tb.hidden) {
CGRect temp_frame = frame;
frame.size.height = 1;
self.tb.frame = temp_frame;
self.tb.hidden = NO;
[UIView animateWithDuration:0.3
animations:^{
self.tb.frame = frame;
} completion:^(BOOL finished) {
}];
}
else
{
[UIView animateWithDuration:0.3
animations:^{
CGRect temp_frame = frame;
frame.size.height = 1;
self.tb.frame = temp_frame;
} completion:^(BOOL finished) {
self.tb.hidden = YES;
}];
}
try this
-(IBAction) hideTable : (id) sender;
{
if(self.tb.hidden==true)
{
self.tb.alpha=1;
[self.tb setHidden:NO];
[UIView animateWithDuration:.25
animations:^{
self.tb.frame = CGRectMake(0,50,320,400); // your req frame
}
];
}
else
{
[UIView animateWithDuration:0.35
animations:^{
self.tb.frame = CGRectMake(0, self.view.frame.size.height+10, 320,400); // y point of yor tableview is more than view bounds so it goes down
}
completion:^(BOOL finished){
[self.tb setHidden:YES];
}
];
}
}
You can use the below methods:
1. (void)showDropDownTable:- To show tableview with top to bottom animation.
2. (void)hideDropdownTable:- To hide tableview with bottom to top animation.
- (IBAction)btnDropDownClicked:(id)sender
{
if (checkShowHideTableView == TRUE)
{
[self hideTableView];
}
if (self.tblDropDown.hidden)
{
[self showDropDownTable];
}
else
{
[self hideDropdownTable];
}
}
- (void)showDropDownTable
{
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationCurveEaseOut animations: ^{
_tblDropDown.frame = CGRectMake(self.tblDropDown.frame.origin.x, self.tblDropDown.frame.origin.y, self.tblDropDown.frame.size.width, kDropdownTableHeight);
self.tblDropDown.hidden = FALSE;
} completion: ^(BOOL finished) {
}];
}
- (void)hideDropdownTable
{
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationCurveEaseOut animations: ^{
_tblDropDown.frame = CGRectMake(self.tblDropDown.frame.origin.x, self.tblDropDown.frame.origin.y, self.tblDropDown.frame.size.width, 0);
} completion: ^(BOOL finished) {
self.tblDropDown.hidden = TRUE;
}];
}
Hope this can help you.
Try the following
[self.tb beginUpdates];
[UIView animateWithDuration:0.5
delay:0.1
options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
animations:^{
if(self.tb.isHidden)
{
self.tb.alpha=1;
[self.tb setHidden:NO];
}
else
{
self.tb.alpha=0;
[self.tb setHidden:YES];
}
}
completion:^(BOOL finished) {
}];
[self.tb endUpdates];
I'm trying to do a custom presentation animation but the VC is an MPMoviePlayerViewController. I was following this tutorial:
When I present the MPMoviePlayerViewController, all is right.
But when I dismiss it, simply the method:
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
is not called, what I'm doing wrong?
This is the code:
- (IBAction)playButtonTouchedIn:(id)sender {
NSURL *url = [[NSBundle mainBundle]URLForResource:#"Learn to speak Spanish quickly with funny videos on youtube" withExtension:#"mp4"];
MPMoviePlayerViewController *player = [[MPMoviePlayerViewController alloc]initWithContentURL:url];
player.transitioningDelegate = self;
player.modalTransitionStyle = UIModalPresentationCustom;
[player.moviePlayer setShouldAutoplay:NO];
player.moviePlayer.controlStyle = MPMovieControlStyleNone;
[self presentViewController:player animated:YES completion:^{
[player.moviePlayer play];
player.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;
}];
}
The UIViewControllerTransitioningDelegate methods:
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
self.presenting = YES;
return self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
self.presenting = NO;
return self;
}
The UIViewControllerAnimatedTransitioning Methods
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.7;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
if (self.isPresenting) {
[fromViewController.view setUserInteractionEnabled:NO];
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
CGRect startFrame = toViewController.view.frame;
toViewController.view.frame = (CGRect){CGPointMake(toViewController.view.frame.size.width, 0),startFrame.size};
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:4.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
toViewController.view.frame = startFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
[fromViewController.view setUserInteractionEnabled:YES];
}];
}
else{
[toViewController.view setUserInteractionEnabled:YES];
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:fromViewController.view];
CGRect finalFrame = (CGRect){CGPointMake(-fromViewController.view.frame.size.width, 0),fromViewController.view.frame.size};
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.frame = finalFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
I have tried with a view controller developed by me and it works great, so is should be the MPMoviePlayerViewController?
The way I was able to get it to animate properly on dismissal was to re-set the transitioningDelegate and modalTransitionStyle right before calling [presentingViewController dismissViewControllerAnimated:YES];
So instead of just
- (IBAction) closePressed:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES];
}
You would do
- (IBAction) closePressed:(id)sender {
MPCustomModalTransitionDelegate *delegate = [[MPCustomModalTransitionDelegate alloc]init];
self.transitioningDelegate = delegate;
self.modalTransitionStyle = UIModalPresentationCustom;
[self.presentingViewController dismissViewControllerAnimated:YES];
}
The obvious difference here is the addition of a new MPCustomModalTransitionDelegate class, which you would need to refactor out of your view controller.
I have taken some code from other SO questions, but I must be doing something wonky. The app jumps from the first view to the third. What I'm trying to achieve is:
ViewController 1 Image 1 - loads with an image. Quickly cross-disolves into ViewController 1 image 2.
ViewController 1 Image 2 - flips to ViewController 2.
The cross-disolve happens but takes me to VC2. I've battered at this for most of the day. It's time to ask for help while I go sit in a bathtub.
Here's what I've been doing:
- (void)viewDidLoad
{
NSLog(#"%s", __FUNCTION__);
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"%s", __FUNCTION__);
sleep (2);
[self transition1]; //showing image 1
}
- (void) transition1 {
NSLog(#"%s", __FUNCTION__);
/*
[UIView transitionFromView:firstView
toView:secondView
duration:3.0
options:UIViewAnimationOptionTransitionCrossDissolve
completion:^(BOOL finished) {
[firstView removeFromSuperview];
}];
*/
//this transition doesn't happen
UIImage * secondImage = [UIImage imageNamed:#"image2.png"];
[UIView transitionWithView:self.firstView
duration:5.0f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.imageView.image = secondImage;
} completion:NULL];
sleep (2);
[self transition2];
}
- (void) transition2 {
NSLog(#"%s", __FUNCTION__);
self.patterns = [[PatternViewController alloc] initWithNibName:#"PatternView_iPad" bundle:nil];
self.patterns.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:patterns animated:YES completion:nil];
}
Thanks for any help.
UPDATE
I've updated my code per Moxy's suggestions as follows:
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"%s", __FUNCTION__);
[self performSelector:#selector(transition1)
withObject:nil
afterDelay:2.0f];
}
-(void)transition1
{
NSLog(#"%s", __FUNCTION__);
UIImage * secondImage = [UIImage imageNamed:#"image2.png"];
[UIView transitionWithView:self.firstView
duration:5.0f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.imageView.image = secondImage;
}
completion:^(BOOL finished){
[self performSelector:#selector(transition2)
withObject:nil
afterDelay:2.0f];
}];
}
-(void)transition2
{
NSLog(#"%s", __FUNCTION__);
self.patterns = [[PatternViewController alloc] initWithNibName:#"PatternView_iPad"
bundle:nil];
self.patterns.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:patterns
animated:YES
completion:nil];
}
All you need to do is to start your second animation in the first animation's completion block.
Sleep() is something you probably shouldn't be using.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"%s", __FUNCTION__);
[self performSelector:#selector(transition1)
withObject:nil
afterDelay:2.0f]
}
-(void)transition1
{
NSLog(#"%s", __FUNCTION__);
UIImage * secondImage = [UIImage imageNamed:#"image2.png"];
[UIView transitionWithView:self.firstView
duration:5.0f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.imageView.image = secondImage;
}
completion:^(BOOL finished){
[self performSelector:#selector(transition2)
withObject:nil
afterDelay:2.0f]
}];
}
-(void)transition2
{
NSLog(#"%s", __FUNCTION__);
self.patterns = [[PatternViewController alloc] initWithNibName:#"PatternView_iPad"
bundle:nil];
self.patterns.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:patterns
animated:YES
completion:nil];
}