I'm trying to write an app that uses custom animations to transition between view controllers in the view stack in a UINavigationController.
Right now, I'm adding multiple laters of the same view, just to get the mechanics up and running properly.
My visual goals are as follows:
1. Create a new "FirstVC".
2. Set the alpha value of [FirstVC view] to 0.
3. Set the transform value of [FirstVC view] to 25% in both directions (vertical & horizontal).
In the animations block, what I'd like to accomplish is:
1. Set the transform value of [OutgoingVC view] to 500% in both directions (blowing it up 5x).
2. Set the alpha value of [OutgoingVC view] to 0 (fading out to nothing).
3. Set the transform value of [FirstVC view] to 1 (bringing it back to its original size).
4. Set the alpha value of [FirstVC view] to 1 (fading in to full color).
The net effect of this animation should be that the "top page" blows up and fades out while the "next/bottom page" blows up (only to full screen) and fades in. The idea is of a user falling through a floor or something. "Going deeper."
I've slowed my animation down to a 10.0 sec interval so I can get a better handle of what's going on.
It appears that the animations taking place on "OutgoingVC" (i.e., the top view) are correct. The bottom view, however, seems to come in fine (100% of screen size and alpha=1.0), but then it keeps going and appears to blow up to either 4x/5x and also fade out to an alpha of 0. Once the screen has gone completely black, the new UIViewController (FirstVC) is correctly displayed on screen.
Does anyone see why my code wouldn't behave the way I want?
Thanks!
Stupid me! Here's the code:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *currentView = [self view];
UIView *newView = [newViewController view];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[newView setAlpha:0];
[UIView transitionWithView:[self view]
duration:10.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self view] addSubview:newView];
[currentView setAlpha:0];
[currentView setTransform:CGAffineTransformMakeScale(5.0, 5.0)];
[newView setAlpha:1.0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
}
completion:^(BOOL finished){
[[self navigationController] pushViewController:newViewController animated:NO];
}
];
}
You have added newView as a subview of currentView, so perhaps the animations you are doing to currentView also apply to newView - it presumably scales up and changes the alpha of its subviews as well, including newView. Could you have the initial scale of newView to be 0.2, and drop the rescaling of newView in the animation block, so it would end up at 1.0 scale? Not sure what to do about the alpha, though...
EDIT after seeing your solution below - glad you've got it working, would it perhaps be cleaner (if you're grabbing images off the screen to handle your views anyway) to have a separate viewcontroller to manage the transition, passing it the two images so it can do the animations internally, so you push it to the front with no animation, perform your animation starting with the first view's image and ending with the second view's image (so you can just do standard animations on two imageViews instead of the whole view of the controller), and then push the second view controller once you are done?
After playing with it some more, I've gotten over one hump and met with another obstacle.
I'm upvoting jrturton's answer because the problem did indeed seem related to the fact that I was adding the new view to [self view].
What I did to get around this was add a buffer view ([self BackView]) that exists between [self view] and its subviews.
Then, then following code works to push a new view controller:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *newView = [newViewController view];
[newView setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[[self view] addSubview:newView];
[UIView transitionWithView:[self view]
duration:1.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self BackView] setTransform:CGAffineTransformMakeScale(5, 5)];
[[self BackView] setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
[newView setAlpha:1];
}
completion:^(BOOL finished){
[newView removeFromSuperview];
[[self navigationController] pushViewController:newViewController animated:NO];
[[self BackView] setTransform:CGAffineTransformIdentity];
[[self BackView] setAlpha:1];
}
];
}
Now, of course, I've run into another problem, namely, popping controllers.
When I pop them, the animation seems to work just fine, but when I get to the end (completion block) and do the following:
[[self navigationController] popViewControllerAnimated:NO];
or even:
[[self navigationController] popViewControllerAnimatedYES];
what I end up with is a blank white screen.
I'm assuming that something I'm doing during my "push animation" is causing this problem, but I can't figure it out! Help!
Thanks!
Related
I'm writing a little card game where 4 cards are dealt to the screen and the user can tap each card to reveal (and again to hide) it.
Each card-front and card-back are stored in image views. A UIButton catches the user tap and should flip the card over.
I've added front and back of the card as subviews to a container view and I use the method UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight for the animation.
Please see the code below, I've removed the handling of the 4 different cards in the methods below for the sake of simplification & readability. Also I've replaced some array/filename-juggling for the cards with static image-names for front and back here.
The strange thing with this code now is the sometimes it works as expected 10 times in a row (i.e. the flipping animation is shown) but sometimes no animation is shown at all (i.e. the other card side is shown but without the flipping.). Then it's the other way round: Sometimes the card is shown without any animation for 7 or 8 times then suddenly the flipping-animation is shown.
It's driving me nuts as I can't see the reason for this strange behaviour.
Do you have any idea? I'm building for iOS 8 to iOS 10.
From the .h file:
#interface GameViewController : UIViewController
{
UIImageView *cardback1;
UIImageView *cardfront1;
UIView *containerView;
BOOL c1Flipped;
// much more...
}
And from the .m file:
-(void)flipCardButtonClicked:(id)sender
{
containerView = [[UIView alloc] initWithFrame: CGRectMake(25,420,220,300)];
[self.view addSubview:containerView];
c1Flipped = !c1Flipped;
cardback1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
cardfront1 = [[UIImageView alloc] initWithFrame: CGRectMake(0,0,220,300)];
if (c1Flipped)
{
cardback1.image = [UIImage imageNamed:#"backside.png"];
cardfront1.image = [UIImage imageNamed:#"frontside.png"];
}
else
{
cardback1.image = [UIImage imageNamed:#"frontside.png"];
cardfront1.image = [UIImage imageNamed:#"backside.png"];
}
[containerView addSubview:cardfront1];
[containerView addSubview:cardback1];
[cardfront1 release];
[cardback1 release];
[self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0];
}
-(void)flipSingleCard
{
[containerView.layer removeAllAnimations];
[UIView beginAnimations:#"cardFlipping" context:self];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(flipDidStop:finished:context:)];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:containerView cache:YES];
[containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[UIView commitAnimations];
}
-(void)flipDidStop:(NSString*)animationID finished:(BOOL)finished context:(void *)context
{
[containerView removeFromSuperview];
[containerView release];
}
My guess is that [self performSelector:#selector(flipSingleCard) withObject:nil afterDelay:0.0]; is the culprit. Seems like this could be a timing issue. Have you tried adding an actual delay to this? Say...0.1? I believe that doing this could fix this, OR just simply calling the method directly instead of using performSelector.
Looks like it indeed was a timing issue as suspected by BHendricks.
Adding a 0.1 delay solved the problem.
Thanks a lot.
I have a UIView called sineWave that renders an animated sine wave. I add sineWave as a subview by doing [self.view addSubview:self.sineWave];. I have another UIView called panedView which is the highest/top view/layer. As panedView is dragged up, sineWave view is revealed. I add panedView above sineWave view by doing [self.view insertSubview:panedView aboveSubview:self.sineWave];. When I pan up everything works perfectly. However, since adding the sineWave view I have an issue swiping up. When I swipe up, I can see that the swipe method gets called, but my view doesn't move up where it should. However, if I swipe a second time immediately after my first swipe, my view will move to where its programmed to - revealing the sine wave. Any idea why this would occur?
- (IBAction)handleUpSwipe:(UISwipeGestureRecognizer *)recognizer
{
NSLog(#"Swipe UP");
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
panedView.frame = CGRectOffset(panedView.frame, 0, -1*self.view.bounds.size.height*.80);
} completion:nil];
[self displaySine];
}
-(void) displaySine
{
if(sineIsDisplaying == NO)
{
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, (.75*self.view.bounds.size.height), 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
[self.sineWave setBackgroundColor:[UIColor clearColor]];
[self.sineWave animateWave];
[self.view addSubview:self.sineWave];
[self.view insertSubview:panedView aboveSubview:self.sineWave];
sineIsDisplaying = YES;
}
}
The native push transition shows the source view, and the transition over to the destination view seamlessly.
The modal transition shows the destination view overlaying the source view from the bottom.
I'd like a modal transition that works with the navigation controller.
So far I have this:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
anim.duration = .2;
anim.autoreverses = NO;
anim.removedOnCompletion = YES;
anim.fromValue = [NSNumber numberWithInt:sourceViewController.view.frame.size.height];
anim.toValue = [NSNumber numberWithInt:0];
[sourceViewController.navigationController.view.layer addAnimation:anim
forKey:kCATransition];
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
This is in the -perform method in my segue subclass. The problem with this is that the navigation controller push is done almost immediately, and while the transition takes place, nothing of the source view is displayed. I want it to look as if it's overlaying.
I thought it might be possible to take a screenshot using Core Graphics and having that as a superview of the destination view, but I couldn't get it to work properly.
I also tried using one of the UIView animation methods like so:
[sourceViewController.view addSubview:destinationController.view];
[destinationController.view setFrame:sourceViewController.view.window.frame];
[destinationController.view setTransform:CGAffineTransformMakeTranslation(0, sourceViewController.view.frame.size.height)];
[destinationController.view setAlpha:1.0];
[UIView animateWithDuration:1.3
delay:0.0
options:UIViewAnimationOptionTransitionNone
animations:^{
[destinationController.view setTransform:CGAffineTransformMakeTranslation(0, 0)];
[destinationController.view setAlpha:1.0];
}
completion:^(BOOL finished){
[destinationController.view removeFromSuperview];
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
}];
But again, there's an issue with this: the navigation bar isn't displayed until the view controller is actually pushed onto the navigation stack. So there's a sort of "jump" at the end of the animation when the navigation bar is suddenly displayed.
So what are my options with this?
I resolved this by taking the route of creating an image of the source view and transitioning over that.
Also, it should be noted that the "flash" doesn't exist on ios 7 so there isn't much custom code necessary.
I use a UIView animation to randomly animate 5 squares (UIButtons) around the screen. Depending on a user selection, there are anywhere from 2 to 5 squares visible. When only 2 are visible, the other three's hidden values get set to YES, so they are actually still animating (right?), they just aren't visible. But when only 2 are visible, the animation is smooth, but when all five are visible, the animation gets choppy. I'm not really sure how to describe it, because the squares are still moving at the correct speed and moving to the correct points; the choppiness isn't terrible, just bad enough to be noticeable. Is there any way to get rid of it? This is the code I use to animate the squares:
Edit: changed animations to block:
[UIView animateWithDuration:animationSpeed
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
view.center = destPoint;
}
completion:^(BOOL finished){
if([view isEqual:squareThree])
[self moveBadGuys];
}
];
/*for(UIButton* button in squareArray) {
if(!shouldMove)
return;
[UIView beginAnimations:#"b" context:nil];
[UIView setAnimationDuration:animationSpeed];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
view.center = destPoint;
[UIView commitAnimations];
}*/
Edit: the view presenting this is the third in a stack of three UIViewController presented with
ViewController* controller = [[[ViewController alloc] init] autorelease];
[self presentModalViewController:controller animated:NO];
Does this way of presenting views eat up memory?
There are a few things that can cause this. It always comes down to how complex the content is. Also, simulator can be really bad about handling animation, so be sure you are testing on real hardware.
Are there large images on the buttons? Are the buttons casting shadows? Those things can slow it down.
Also- use block based animation. Not the old begin-commit methods.
Not exactly sure why it's slow, but have you tried nesting the thing differently?
if(!shouldMove)
return;
[UIView beginAnimations:#"b" context:nil];
[UIView setAnimationDuration:animationSpeed];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
for(UIButton* button in squareArray) {
view.center = destPoint;
}
[UIView commitAnimations];
does (almost - the logic is a bit different in the !shouldMove case, but that's a different story) the same, but in a cleaner way.
I am brand new to Core Animation, and I need to know how to do 2 animations:
I need to switch XIBs by fading through black (fully releasing the the first view controller)
I need to mimic the UINavigationController's pushViewController animation (switching XIBs and releasing the first view controller)
How can you achieve these animated view transitions?
I've done both of these animations, but maybe not in the exact way you are looking for.
Fade View to black, I took this the other way an instead added a new
subview that covered the entire window that was Black and animated
the Alpha from 0.0 to 1.0. Made for a nice effect.
[UIView animateWithDuration:0.5
animations:^{ _easterEgg.alpha = 1.0; }
completion:^(BOOL finished) { [self animateIndex:0]; }];
Slide in a view like UINavigationController. I didn't do this exactly like UINavigationController since it does multiple animations, but I did have a new view slide the previous view off screen. This code sets the frame of the new view off screen to the right of the current view, builds a frame location that is off the screen to the left, and grabs the current visible frame. Finally it just animates the new view from off screen right into the visible frame, and the old view from the visible frame to off left. Then removes the old view.
CGRect offRight = CGRectMake(_contentView.frame.size.width,
0,
_contentView.frame.size.width,
_contentView.frame.size.height);
CGRect offLeft = CGRectMake(-_contentView.frame.size.width,
0,
_contentView.frame.size.width,
_contentView.frame.size.height);
CGRect visibleFrame = CGRectMake(0, 0, _contentView.frame.size.width, _contentView.frame.size.height);
[view setFrame:offRight];
UIView *currentView = [[_contentView subviews] lastObject];
[_contentView addSubview:view];
[UIView animateWithDuration:0.5
animations:^{
[currentView setFrame:offLeft];
[view setFrame:visibleFrame];
}
completion:^(BOOL finished) {
[currentView removeFromSuperview];
}];