iOS 9: push/pop view controller broken by edgesForExtendedLayout - ios

I have found a strange issue while debugging on iOS 9. In my View Controller's viewDidLoad I have this piece of code:
if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
When I am pushing or popping a new view controller I have noticed a strange animation. The transition isn't smooth and it seems like it is displaying a "stack" of view controllers (you can notice the top and even slightly a bottom of both images).
I had no problem on iOS 7 or iOS8, is this a bug of the newly released iOS 9 or am I missing something?
It's also worth to mention that I am using custom animation for push/pop view controller transition.
Here is an example of one of the overriden UINavigationController methods
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIView *theWindow = self.view;
[[theWindow layer] removeAllAnimations];
if (animated) {
CATransition *animation = [CATransition animation];
[animation setDuration:0.25];
[animation setType:kCATransitionFade];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[[theWindow layer] addAnimation:animation forKey:#""];
}
[super pushViewController:viewController animated:NO];
}
EDIT:
I have been debugging this issue for couple of hours now. I have reimplemented the custom animations following API for view controllers transitions introduced in iOS 7 (navigationController:animationControllerForOperation:operation fromViewController:toViewController) but the issue persists.
The problem happens if the transition is animating alpha property.
Do you have an idea how to overcome this issue?
Thanks.

You need to setup final frame for toViewController. transitionContext.finalFrame(for:) can help you to determine it. Something like:
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)

Related

Creating transitions on the navigation stack that show both source and destination views?

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.

How to animate view after using UIModalPresentationCurrentContext

I was looking around for a solution on how to wrap a view in a dialog in iOS when I came across this post, which has this line:
vc.modalPresentationStyle = UIModalPresentationCurrentContext;
It solves my problem of basically creating/imitating a dialog but it does not animate upon transition as mentioned in the post. So what is the simplest way to get the slide up animation?
ps.I would ask this as a sub question in that post but I do not have 50 rep comment :(
Well, once your view has been shown, you can do pretty much any animation you want in it. You can do a simple [UIView animateWithDuration] kind of deal, but I would personally use a CATransition for this, it's relatively simple.
The Way of the QuartzCore
First, I'm gonna assume that the view you're presenting is transparent, and there's another view inside that behaves as the dialog. The view controller that will be presented, let's call it PresentedViewController and holds the dialog property for the view within.
PresentedViewController.m
(Needs to be link against QuartzCore.h)
#import <QuartzCore/QuartzCore.h>
#implementation PresentedViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (animated)
{
CATransition *slide = [CATransition animation];
slide.type = kCATransitionPush;
slide.subtype = kCATransitionFromTop;
slide.duration = 0.4;
slide.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
slide.removedOnCompletion = YES;
[self.dialog.layer addAnimation:slide forKey:#"slidein"];
}
}
Getting Fancy
The good thing about this, is that you can create your own custom animations, and play around with other properties.
CABasicAnimation *animations = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D transform;
// Take outside the screen
transform = CATransform3DMakeTranslation(0, self.view.bounds.size.height, 0);
// Rotate it
transform = CATransform3DRotate(transform, M_PI_4, 0, 0, 1);
animations.fromValue = [NSValue valueWithCATransform3D:transform];
animations.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animations.duration = 0.4;
animations.fillMode = kCAFillModeForwards;
animations.removedOnCompletion = YES;
animations.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0 :0.0 :0 :1];
[self.dialog.layer addAnimation:animations forKey:#"slidein"];
Here, the view will be moved outside of screen by the translation, then rotated, and it will slide in, back to its original transform. I also modified the timing function to provide a smoother curve.
Consider that I just scraped the surface of what's possible with CoreAnimation, I've been on this road for three years now, and I've grown to like CAAnimation for all the things it does.
Storyboard pro-tip: You can wrap this up very nicely if you build your own custom UIStoryboardSegue subclass.

UIView Animation slide in

I am trying to get my UIView to slide in from the right in the viewDidLoad method here's what I've got.
CGRect menuFrame = self.menu.frame;
menuFrame.origin.y = menuFrame.size.height+200;
[UIView animateWithDuration:1
delay:0.05
options: UIViewAnimationCurveEaseIn
animations:^{
self.menu.frame = menuFrame;
}
completion:^(BOOL finished){
NSLog(#"Animation Complete!");
}];
I am not sure what has gone wrong here I appreciate any help greatly.
This isn't what you asked, but it is worth saying anyway: viewDidLoad is a bad place for this code. viewDidLoad is called because the view controller has obtained its view, not because that view has appeared or will soon appear in the interface. It might soon appear in the interface, which is why your code has seemed to work up until now, but it might not.
The correct place for this code is probably viewWillAppear or viewDidAppear. You probably want to call this code only once, though, the first time the view appears. If you want to prevent the code from being called, because you've already animated menu into view on an earlier viewDidAppear call, simply include a conditional check to see whether menu already has a frame within the visible view.
By the way, another reason for avoiding things having to do with frames and bounds in viewDidLoad is that if your app launches into landscape and this is your root view, x and y are reversed.
It looks like you are trying to slide your menu in from the bottom as the Y position is pushed down initially by 200.
Usually you start by adding the view as a subview in its offscreen position and then set the onscreen position in the animation block.
And, make sure that you pass 1.0 as the animation duration.
use those transition.subtype for different effects
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.subtype = kCATransitionFromLeft; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[appDelegate.navigation.view.layer addAnimation:transition forKey:nil];
viewController *DairyVC = [[viewController alloc] initWithNibName:#"viewController" bundle:nil];
[appDelegate.navigation pushViewController:DairyVC animated:YES];

iOS: UITableView reload animation

I have implemented nested UITableViews such that, on selecting a particular row from parent Data list, gets child nodes data from server and reloads them in same UITableview. I have given provision to go back to parent nodes using dictionary and everything working fine. I want Navigation Style animation when I move parent data list to child data list. Could someone please help me how to do this.
I have tried using following code UIViewAnimationOptionTransitionFlipFromLeft and UIViewAnimationOptionTransitionFlipFromRight animations it flips the view - I am looking for similar but without flip - simple navigation style animation like I want to pretend I am pushing another UITableView.
[UIView transitionWithView: self.listTableView
duration: 0.35f
options: UIViewAnimationOptionTransitionFlipFromLeft
animations: ^(void) {
[self.listTableView reloadData];
} completion: ^(BOOL isFinished){
}];
Here is Logic with Code.
you need to import QuartzCore framework in.h` for using below piece of Code.
AS #import
// Method to replace a given subview with another using a specified transition type, direction, and duration
-(void)replaceSubview:(UIView *)oldView withSubview:(UIView *)newView transition:(NSString *)transition direction:(NSString *)direction duration:(NSTimeInterval)duration
{
// Set up the animation
CATransition *animation = [CATransition animation];
// Set the type and if appropriate direction of the transition,
if (transition == kCATransitionFade)
{
[animation setType:kCATransitionFade];
}
else
{
[animation setType:transition];
[animation setSubtype:direction];
}
// Set the duration and timing function of the transtion -- duration is passed in as a parameter, use ease in/ease out as the timing function
[animation setDuration:duration];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[oldView layer] addAnimation:animation forKey:#"transitionViewAnimation"];
}
Call Above method when TableView row gets Clicked as below.
[self replaceSubview:thisTableView
withSubview:thisTableView
transition:kCATransitionPush
direction:kCATransitionFromLeft
duration:0.3];
//thisTableView is instance of UItableView.

iOS Using Partial Page Curl on UIView, NOT UIViewController

I've got a MainViewController that has many subViews. Basically, it's broken down into two panes. The panes are designed to look like clipboards. I'd like to hit a button and have the "paper" on one of the clipboards do a partial curl to reveal the page underneath.
This isn't a full Modal ViewController presentation situation.
I'm working with the following code, but the results are funky:
FlipView *flipView = [[FlipView alloc] init];
[self setFlipView:flipView];
[flipView setHidden:YES];
[flipView setFrame:[[self WhereAmI] frame]];
[[self view] addSubview:flipView];
[[self view] bringSubviewToFront:[self WhereAmI]];
[flipView setHidden:NO];
CATransition *animation = [CATransition animation];
[animation setDelegate:self];
[animation setDuration:1.0];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[animation setType:#"pageCurl"];
[animation setFillMode:kCAFillModeForwards];
[animation setEndProgress:0.9];
[animation setRemovedOnCompletion:NO];
[[self WhereAmI] setHidden:YES];
[[[self WhereAmI] layer] addAnimation:animation forKey:#"pageCurlAnimation"];
Basically, I'm trying to load my new view programmatically, slide it in behind the currently visible view and then curl up the view that's on top to reveal the view that's now underneath. I get somewhat of a partial curl animation, but the "page" that's curling up isn't opaque, which is odd. Also, when I don't set my view and label to be hidden, even though you see them curl up, you also still see them "underneath", which is also odd. Plus, I don't see the UILabel I placed in the XIB for the new view.
Any thoughts?
Thanks!
OK, so I've got a working solution, but it's not quite what I was hoping to do. I'd still love to figure out the solution to my original question, but here's what I'm doing for now:
The background image for my "base" view is an image of a spiral-bound notebook.
The background image for all other views is a screen shot of a page curl.
(By "background image", I mean a UIColor created with a patternImage.)
The code to transition views is:
- (IBAction)NavigationTabSelected:(id)sender
{
// Flipping views is driven by a SegmentedControl
int selected = [[self NavigationTabsSegmentedControl] selectedSegmentIndex];
int current = [[self ViewArray] indexOfObject:[self ActiveFlipView]];
UIView *newFlip = [[self ViewArray] objectAtIndex:selected];
BOOL up = selected > current;
[UIView animateWithDuration:1.0
animations:^{
[UIView setAnimationTransition:(up)?UIViewAnimationTransitionCurlUp:UIViewAnimationTransitionCurlDown forView:[self RightView] cache:YES];
[[self ActiveFlipView] removeFromSuperview];
[[self RightView] addSubview:newFlip];
}
completion:^(BOOL finished){
[self setActiveFlipView:newFlip];
}];
}
As you can see, tabs to the right of the current tab generate a "curl up" and tabs to the left of the current tab generate a "curl down". It's not quite what I wanted, but it serves my purpose.
Any better ideas?
have you considered loading it as a modal view?
- (void)yourFrontViewControllerMethod
{
YourBackViewController *bfvc = [[YourBackViewController alloc] init];
[bfvc setModalTransitionStyle:UIMoalTransitionStylePartialCurl];
[self presentViewController:bfvc];
[bvfc release];
}
This will give you the desired effect of a view curling up to reveal another view.
Also as a general rule of thumb we try not to create animations that way. Try using a block based method instead (eg [UIView AnimateWithDuration:....]);
Hope that helps :)

Resources