Interactive UIViewController Transition with new "usingSpringWithDamping:initialSpringVelocity"-animation - objective-c

I try to implement a interactive custom modal UIViewController transition using the new iOS7 APIs. Everything is working as desired as long as I don't use the new
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
method for animating the transition. It is working fine if I invoke the transition via a button. The problem occurs when the transition is interactive, driven by a GestureRecognzier. If I release my finger the animation is immediately cut of and won't finish. The same code is working if I use
animateWithDuration:delay:options:animations:completion:
instead.
Here is the code which animates the transition:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect fullFrame = transitionContext.containerView.frame;
if (!self.dismissal) {
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
fromViewController.view.frame = fullFrame;
toViewController.view.frame = CGRectMake(0, fullFrame.size.height, 0, 0);
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0.5 options:UIViewAnimationOptionTransitionNone animations:^{
//animation
toViewController.view.frame = CGRectMake(0,0, fullFrame.size.width, fullFrame.size.height);
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else {
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:fromViewController.view];
fromViewController.view.frame = fullFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:.5 initialSpringVelocity:.5 options:0 animations:^{
fromViewController.view.frame = CGRectMake(0, fullFrame.size.height, fullFrame.size.width, fullFrame.size.height);
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
//This is working fine
// [UIView animateWithDuration:[self transitionDuration:transitionContext]/2 animations:^{
// fromViewController.view.frame = CGRectMake(0, fullFrame.size.height, fullFrame.size.width, fullFrame.size.height);
// } completion:^(BOOL finished) {
// [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
// }];
}
This is the code behind the gesture recogniser:
#pragma mark - Gesture recognition
- (void)handlePan:(UIScreenEdgePanGestureRecognizer *)pgr
{
CGPoint translation = [pgr translationInView:pgr.view];
CGFloat percentage = fabs(translation.x / CGRectGetWidth(pgr.view.frame));
switch (pgr.state) {
case UIGestureRecognizerStateBegan:
self.interactionInProgress = YES;
[self.respondingVC proceedToNextViewController];
break;
case UIGestureRecognizerStateChanged: {
[self updateInteractiveTransition:percentage];
break;
}
case UIGestureRecognizerStateEnded:
if(percentage < 0.5) {
[self cancelInteractiveTransition];
} else {
[self finishInteractiveTransition];
}
self.interactionInProgress = NO;
break;
case UIGestureRecognizerStateCancelled:
[self cancelInteractiveTransition];
self.interactionInProgress = NO;
default:
break;
}
}

It looks like you're using UIPercentDrivenInteractiveTransition. When you call finishInteractiveTransition, UIPercentDriventInteractiveTransition does not call animateTransition.
You should override both updateInteractiveTransition and finishInteractiveTransition to update your view states.
All my attempts to begin an animation block inside an interactive transition have ended in failure. I finally at least got it to finish the animation by calling [self updateInteractiveTransition:1] in finishInteractiveTransition. Otherwise the views will be left in the state they were in and the transition never completes or the transition is treated as completed but your views are in strange places.

Related

uiview animateWithDuration cant hide in same toggle method

Trying to hide and show a view from a single method but it seems like it should work to show and hide but its instant.
- (void)toggleMyView:(BOOL)show
{
CGRect orig = self.originalMyViewFrame; // original frame of the view
CGRect offScreen = CGRectMake(orig.origin.x, self.view.frame.size.height, orig.size.width, orig.size.height);
if (show) {
self.myView.frame = offScreen; // set off screen
self.myView.hidden = NO; // unide the view
[UIView animateWithDuration:0.5 animations:^{
self.myView.frame = orig; // animate to original position
}];
} else {
[UIView animateWithDuration:0.3 animations:^{
self.myView.frame = offScreen; // animate off screen
} completion:^(BOOL finished) {
self.myView.hidden = YES; // hide when animation finished
}];
}
}
If I remove the hide logic it animates in nicely.
- (void)toggleMyView:(BOOL)show
{
CGRect orig = self.originalMyViewFrame; // original frame of the view
CGRect offScreen = CGRectMake(orig.origin.x, self.view.frame.size.height, orig.size.width, orig.size.height);
if (show) {
self.myView.frame = offScreen; // set off screen
self.myView.hidden = NO; // unide the view
[UIView animateWithDuration:0.5 animations:^{
self.myView.frame = orig; // animate to original position
}];
} else {
self.myView.hidden = YES; // hide when animation finished
}
}
I dont see why adding the hide logic breaks.
Could this be a memory retain cycle problem?

How to create a custom timing function?

I am busy with a custom animated transition between UIViewControllers and I like the the UIView to jump and then fill up the space similar to the following screenshot:
Now there isn't an "Option" I could create such an effect. Now I could create a keyframe animation with one key at the top and one in the center but that wouldn't feel really natural, it would feel robotic.
How would one approach this?
SOURCE (SO FAR)
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
if ([fromVC isEqual:self])
{
[_activeCardView.superview bringSubviewToFront:_activeCardView];
[UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext]
delay:0.0
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
[_activeCardView setFrame:CGRectMake(0.0, 0.0, toVC.view.bounds.size.width, toVC.view.bounds.size.height)];
}
completion:^(BOOL finished){
[container addSubview:toVC.view];
[transitionContext completeTransition:YES];
}];
}
else
{
[container addSubview:toVC.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
[_activeCardView setFrame:_basicCardFrame];
}
completion:^(BOOL finished){
[transitionContext completeTransition:YES];
}];
}
}
For a smooth 'jump' animation you could just chain two animations with 'EaseInOut' curves.
[UIView animateWithDuration:<DURATION>/2 delay:<DELAY> options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
//move view up
}completion:^(BOOL finished) {
[UIView animateWithDuration:<DURATION>/2 delay:<DELAY> options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
//move view down
}completion:NULL];
}];

iAd shows white box

I have a little problem with iAd. Sometimes it shows a blank white box. When I try it with Admob it works perfect. Sometimes the banner comes correctly from the button but sometimes it shows the white box immediately. What is the issue?
Here is my code:
-(void)viewDidLayoutSubviews
{
if (self.view.frame.size.height != self.iAdBannerView.frame.origin.y)
{
self.iAdBannerView.frame = CGRectMake(0.0, self.view.frame.size.height, self.iAdBannerView.frame.size.width, self.iAdBannerView.frame.size.height);
iAdBannerView.requiredContentSizeIdentifiers= [NSSet setWithObjects: ADBannerContentSizeIdentifierLandscape,nil];
iAdBannerView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
}
if (self.view.frame.size.height != self.gAdBannerView.frame.origin.y)
{
self.gAdBannerView.frame = CGRectMake(150, self.view.frame.size.height, self.gAdBannerView.frame.size.width, self.gAdBannerView.frame.size.height);
}
// Hide the banner by sliding down
-(void)hideBanner:(UIView*)banner
{
if (banner && ![banner isHidden])
{
[UIView beginAnimations:#"hideBanner" context:NULL];
banner.frame = CGRectOffset(banner.frame, 0, banner.frame.size.height);
[UIView commitAnimations];
banner.hidden = TRUE;
}
}
// Show the banner by sliding up
-(void)showBanner:(UIView*)banner
{
if (banner && [banner isHidden])
{
[UIView beginAnimations:#"showBanner" context:NULL];
banner.frame = CGRectOffset(banner.frame, 0, -banner.frame.size.height);
[UIView commitAnimations];
banner.hidden = FALSE;
}
}
#pragma mark - ADBanner delegate methods -
// Called before the add is shown, time to move the view
- (void)bannerViewWillLoadAd:(ADBannerView *)banner
{
NSLog(#"iAd load");
[self hideBanner:self.gAdBannerView];
[self showBanner:self.iAdBannerView];
}
// Called when an error occured
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"iAd error: %#", error);
[self hideBanner:self.iAdBannerView];
[self.gAdBannerView loadRequest:[GADRequest request]];
}
iAds doesn't always load an ad. There's a delegate method to let you know when an ad loads successfully.
ADBannerViewDelegate has bannerViewDidLoadAd: and bannerView:didFailToReceiveAdWithError:.

Hide UINavBar and UITabBar at the same time with animation

Edit: I awarded the bounty to john since he put a lot of effort into his answer, and would get it anyways, but there's still no working solution. I am still looking for an answer, if someone knows how to do this it'd be greatly appreciated.
I want to add a "maximize" button to my app that hides the navigation and tab bar. The navbar and tabbar should slide in/out smoothly, and the inner/content view should also expand and shrink at the same rate as the navbar and tabbar.
I used [self.navigationController setNavigationBarHidden: YES/NO animated: YES]; for the navbar and found this thread How to hide uitabbarcontroller for hiding the tabbar.
UITabBar class extension:
- (void) setTabBarHidden:(BOOL)hidden animated:(BOOL)animated {
CGRect screenRect = [[UIScreen mainScreen] bounds];
float screenHeight = screenRect.size.height;
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
screenHeight = screenRect.size.width;
}
if (!hidden) {
screenHeight -= self.tabBar.frame.size.height;
}
[UIView animateWithDuration: (animated ? UINavigationControllerHideShowBarDuration : 0) animations: ^{
for (UIView* each in self.view.subviews) {
if (each == self.tabBar) {
[each setFrame: CGRectMake(each.frame.origin.x, screenHeight, each.frame.size.width, each.frame.size.height)];
} else {
[each setFrame: CGRectMake(each.frame.origin.x, each.frame.origin.y, each.frame.size.width, screenHeight)];
}
}
} completion: ^(BOOL finished) {
NSLog(#"Animation finished %d", finished);
}];
}
The problem is when I use the two at the same time (hiding/showing the nav and tab bar), it's not clean. If the navbar comes first, anything anchored to the bottom jumps (see example below), and if the tabbar comes first, the top jumps.
Example: I position the UIButton in the bottom right and set its autoresizing mask
resizeButton.frame = CGRectMake(self.view.bounds.size.width - 50, self.view.bounds.size.height - 100, 32, 32); // hardcoded just for testing purposes
resizeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
But when the navbar and tabbar are minimized the UIButton jumps between the two states (doesn't slide along with the tab bar). However, if I change it to attach to the top right, it slides perfectly with the nav bar.
Does anyone know how to solve this?
Edit:
This is the closet and most elegant solution I have so far (just trying to get a working concept):
[UIView animateWithDuration: UINavigationControllerHideShowBarDuration animations: ^{
if (self.isMaximized) {
self.tabBarController.view.frame = CGRectMake(0, 20, screenRect.size.width, screenRect.size.height + 49 - 20);
[self.navigationController setNavigationBarHidden:YES animated:YES];
} else {
self.tabBarController.view.frame = CGRectMake(0, 20, screenRect.size.width, screenRect.size.height - 20);
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
} completion: ^(BOOL finished) {
NSLog(#"Frame done: %#", NSStringFromCGRect(self.view.frame));
return;
}];
On maximizing:
Slides the navbar up, and slides the tabbar down, at the same time
The top of the inner/content view slides up, and the bottom of this view jumps down
On minimizing:
Slides the navbar down, and slides the tabbar up, at the same time
The top of the inner/content view slides down properly, but the bottom jumps to the final value, leaving whitespace which is then covered by the sliding tabbar
If I rearange the order of the minimizing-animations (so the navbar animatino is called first), then the top in the inner/content view jumps
the solution i use should eliminate the jump problem you see.
this solution is derived from an Objective-C category found Carlos Oliva's github page, and while the copyright in that code is "all rights reserved", i wrote him and he provided permission for use.
my category code varies only slightly from his code. also, find below the category code the invocation code that i use in my app.
from UITabBarController+HideTabBar.m
// the self.view.frame.size.height can't be used directly in isTabBarHidden or
// in setTabBarHidden:animated: because the value may be the rect with a transform.
//
// further, an attempt to use CGSizeApplyAffineTransform() doesn't work because the
// value can produce a negative height.
// cf. http://lists.apple.com/archives/quartz-dev/2007/Aug/msg00047.html
//
// the crux is that CGRects are normalized, CGSizes are not.
- (BOOL)isTabBarHidden {
CGRect viewFrame = CGRectApplyAffineTransform(self.view.frame, self.view.transform);
CGRect tabBarFrame = self.tabBar.frame;
return tabBarFrame.origin.y >= viewFrame.size.height;
}
- (void)setTabBarHidden:(BOOL)hidden {
[self setTabBarHidden:hidden animated:NO];
}
- (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated {
BOOL isHidden = self.tabBarHidden;
if (hidden == isHidden)
return;
UIView* transitionView = [self.view.subviews objectAtIndex:0];
if (!transitionView)
{
#if DEBUG
NSLog(#"could not get the container view!");
#endif
return;
}
CGRect viewFrame = CGRectApplyAffineTransform(self.view.frame, self.view.transform);
CGRect tabBarFrame = self.tabBar.frame;
CGRect containerFrame = transitionView.frame;
tabBarFrame.origin.y = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
containerFrame.size.height = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
[UIView animateWithDuration:kAnimationDuration
animations:^{
self.tabBar.frame = tabBarFrame;
transitionView.frame = containerFrame;
}
];
}
from my ScrollableDetailImageViewController.m
- (void)setBarsHidden:(BOOL)hidden animated:(BOOL)animated
{
[self setTabBarHidden:hidden animated:animated];
[self setStatusBarHidden:hidden animated:animated];
// must be performed after hiding/showing of statusBar
[self.navigationController setNavigationBarHidden:hidden animated:animated];
}
- (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated
{
id parent = self.navigationController.parentViewController;
if ([parent respondsToSelector:#selector(isTabBarHidden)]
&& hidden != [parent isTabBarHidden]
&& [parent respondsToSelector:#selector(setTabBarHidden:animated:)])
[parent setTabBarHidden:hidden animated:animated];
}
Just try this code, If it is working. I have written this code a year before. But, still it works good for me.
I didn't used the block based animations. Because it was written when I am new to iOS. Just try and optimize yourself as you wanted.
- (void) hideTabBar:(UITabBarController *) tabbarcontroller {
[self.navigationController setNavigationBarHidden:YES animated:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1];
for(UIView *view in tabbarcontroller.view.subviews)
{
if ([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
}
}else if([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 1024, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 1024)];
}
}
}
[UIView commitAnimations];
}
// Method shows the bottom and top bars
- (void) showTabBar:(UITabBarController *) tabbarcontroller {
[self.navigationController setNavigationBarHidden:NO animated:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1];
for(UIView *view in tabbarcontroller.view.subviews)
{
if ([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 430, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 436)];
}
}else if([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 975, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 980)];
}
}
}
[UIView commitAnimations];
}
Try this
you can hide tabbar contrller and navigation bar using animation like:-
-(IBAction)hide:(id)sender
{
[self hideShowBars];
}
- (void) hideShowBars
{
CGRect rect = self.navigationController.navigationBar.frame;
CGRect rect1 = self.tabBarController.tabBar.frame;
if(self.navigationController.navigationBar.isHidden)
{
[self.navigationController.navigationBar setHidden:NO];
rect.origin.y=self.view.frame.origin.y;
}
else
{
rect.origin.y=self.view.frame.origin.y-rect.size.height;
}
if(self.tabBarController.tabBar.isHidden)
{
[self.tabBarController.tabBar setHidden:NO];
rect1.origin.y=self.view.frame.size.height-rect1.size.height-rect1.size.height;
}
else
{
rect1.origin.y=self.view.frame.size.height;
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.50];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(hideShowBarsAnimationStopped)];
self.navigationController.navigationBar.frame=rect;
self.tabBarController.tabBar.frame = rect1;
[UIView commitAnimations];
}
- (void) hideShowBarsAnimationStopped
{
if(self.navigationController.navigationBar.frame.origin.y==self.view.frame.origin.y)
return;
if(!self.navigationController.navigationBar.isHidden)
{
[self.navigationController.navigationBar setHidden:YES];
}
if(!self.tabBarController.tabBar.isHidden)
{
[self.tabBarController.tabBar setHidden:YES];
}
}

Trying to get 2 animations to work simultaneously

In the next code there are 2 methods that are called one after the other. The first one makes the center button disappear and the second one makes the tabbar disappear. Separately they are working fine.
My problem is that when I try to call them one after the other hideCenterButton doesn't animate. Instead of rolling to the left of the screen the button just disappears.
-(void)hideCenterButton:(BOOL)animated
{
if(animated){
[UIView animateWithDuration:0.3
delay:0.0f
options:UIViewAnimationCurveLinear
animations:^{
CGRect frame = self.centerButton.frame;
frame.origin.x = -100;
self.centerButton.frame = frame;
}
completion:^(BOOL finished){
}];
}}
...
- (void)hideTabBar:(UITabBarController *) tabbarcontroller
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
//[UIView setAnimationDelay:1];
for(UIView *view in tabbarcontroller.view.subviews)
{
if([view isKindOfClass:[UITabBar class]] || [view isKindOfClass:[UIImageView class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
}
}
[UIView commitAnimations];
}
You have probably hit into some kind of limitation of UIView-based animations. Several things you might try are:
use animateWithDuration also for hideTabBar:: indeed, beginAnimation is the old-way of animating UIView; animateWithDuration is more recent (introduced with iOS4); by using the same animation calls in bot cases you might get better results; this should be straightforward; this should work:
- (void)hideTabBar:(UITabBarController *) tabbarcontroller
{
[UIView animateWithDuration:0.3
delay:0.0f
options:UIViewAnimationCurveLinear
animations:^{
for(UIView *view in tabbarcontroller.view.subviews)
{
if([view isKindOfClass:[UITabBar class]] || [view isKindOfClass:[UIImageView class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
}
else
{
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
}
}
}
completion:nil];
}
rewrite your animation using Core Animation: this is a slightly lower-level mechanism but it allows you to get the best results in terms of performance; this is, e.g., how you could rewrite the first animation:
first you need to provide a callback:
- (void)animationDidStop:(CABasicAnimation*)anim finished:(BOOL)flag {
CALayer* layer = [anim valueForKey:#"animationLayer"];
layer.position = [anim.toValue CGPointValue];
}
then you can animate your button like this:
CABasicAnimation* move = [CABasicAnimation animationWithKeyPath:#"position"];
CGPoint pos = self.centerButton.center;
pos.x -= 100;
move.toValue = [NSValue valueWithCGPoint:pos];
move.duration = 0.3;
move.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
move.removedOnCompletion = NO;
move.fillMode = kCAFillModeForwards;
move.autoreverses = NO;
move.delegate = self;
[move setValue:self.centerButton.layer forKey:#"animationLayer"];
[self.centerButton.layer addAnimation:move forKey:#"buttonPosition"];
Using Core Animation, you will end up writing more code and you will need to allow some time to learn Core Animation basics, but that was the only way I could solve a similar issue I had with two related animation.
Hope it helps.