Sliding a view controller from bottom with a Gesture - ios

I have a question (Objective-C related).
I want to create a sliding from bottom ViewController on top of the Root Map View Controller by a click on the annotation.
1) After a click on the annotation it should slide from bottom and show 10% of the height;
2) after an upward swipe gesture - it should show up to 100% if the user drags it upwards fully;
3) on the downward gesture user should be able to decrease its visible height to 10% again;
4) on the MapView click the bottom ViewController should hide.
I'm including the Visual scheme of the process implementation.
Any ideas are very very appreciated!

check this demo
NHSlidingController
PanningViewController

You can always try to write your own segue animation to transition between ViewControllers. Just make a custom UIStoryboardSegue, and override its perform method. Here is a snippet to start with:
#interface HorizontalSegue : UIStoryboardSegue
#property CGPoint originatingPoint;
#end
#implementation VerticalSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
NSArray* windowArray = [UIApplication sharedApplication].windows;
if (windowArray.count > 0) {
UIWindow* appWindow = [windowArray lastObject];
[appWindow insertSubview:destinationViewController.view aboveSubview:sourceViewController.view];
}
else
[sourceViewController.view addSubview:destinationViewController.view];
// Store original centre point of the destination view
CGPoint originalCenter = destinationViewController.view.center;
// Set center to start point of the button
destinationViewController.view.center = CGPointMake(self.originatingPoint.x, self.originatingPoint.y*3);
[UIView animateWithDuration:0.25
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
destinationViewController.view.center = originalCenter;
}
completion:^(BOOL finished){
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}
#end
Using this you can easily make custom animations. I am not totally sure if the part with adding the ViewController to the window is correct, but this is something that you could try and check/correct yourself.
Another way of animating segues is using CATransition:
-(void)perform
{
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
CATransition* transition = [CATransition animation];
transition.duration = 0.25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionMoveIn; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromLeft; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[destinationController.view.layer addAnimation:transition forKey:kCATransition];
[sourceViewController presentViewController:destinationController animated:NO completion:nil];
}

Related

IOS/Objective-C/Storyboard: Custom Segue from Left to Right Creating Black Bar Between View Controllers

I have subclassed UIStoryboardSegue to create a Left to Right Segue. The segue works, however, there is a black vertical bar or background that is momentarily visible between the two View Controllers that mars the effect. (I want the segue to look like a normal right to left show segue but in the opposite direction). Does anyone know what could be causing this or how to get rid of it? Here is my code:
#import "customSegue.h"
#import "QuartzCore/QuartzCore.h"
#implementation customSegue
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
CATransition* transition = [CATransition animation];
transition.duration = .25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromLeft; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[sourceViewController.navigationController.view.layer addAnimation:transition
forKey:kCATransition];
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
}
#end
Edit:
I thought I would collect some the suggested fixes I have found although none are working for me.
Set the background color of the presenting view controller to white
Set self.definesPresentationContext = YES; on the presenting view controller or check "Defines Context" for the VC in storyboard from accepted answer here.
Specify the context as follows:
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
UIViewController *destinationController = (UIViewController*)[self destinationViewController]
[sourceViewController presentViewController:destinationController animated:NO completion:nil];
from accepted answer here.
What you're seeing is the view's changing opacity near the beginning and end of the animation, so the "black bar" you're seeing is actually the backing window. While it might not be perfect, a quick fix is to change the background color of your window to match the background color of your destination view controller (then set it back after the transition completes if needed).
Here's your code example with the modifications necessary:
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
CATransition* transition = [CATransition animation];
CGFloat animationDuration = 0.25f;
transition.duration = animationDuration;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromLeft; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
transition.fillMode = kCAFillModeForwards;
[sourceViewController.navigationController.view.layer addAnimation:transition
forKey:kCATransition];
// hold onto the previous window background color
UIColor *previousWindowBackgroundColor = sourceViewController.view.window.backgroundColor;
// switch the window background color to match the destinationController's background color temporarily
sourceViewController.view.window.backgroundColor = destinationController.view.backgroundColor;
// do the transition
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
// switch the window color back after the transition duration from above
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// make sure we still have a handle on the destination controller
if (destinationController) {
destinationController.view.window.backgroundColor = previousWindowBackgroundColor;
}
});
}
Here's an example of the transition slowed down to 2seconds:
Slowed down transition
And the animation at 0.25 as you have it in your code:
Your speed transition

Push segue custom animation (Navigation controller)

I have this storyboard :
TabBarController : Tab1 > NavigationController > VC1 > VC2 > VC3
// VC1.m
[self performSegueWithIdentifier:#"VC2" sender:self];
// VC2.m
[self performSegueWithIdentifier:#"VC3" sender:self];
I would like the transition between VC1 and VC2 to be a fade animation, but the transition between VC2 and VC3 the default one.
But I have to do a push segue and not modal to keep the benefits of UINavigationController (unless I'm missing something). All the solution I find for this are using presentViewController.
Try using this CATransition
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
[navigationController.view.layer addAnimation:transition forKey:kCATransition];
isPush ? [navigationController pushViewController:viewController animated:NO] : [navigationController popViewControllerAnimated:NO];
If you insist on a segue than I see 2 ways:
1) Use UIViewControllerAnimatedTransitioning protocol and customize the transitions the way U like.
2) Write a custom UIStoryboardSegue
Or perform you own transitions and animations by means of manipulation of controllers and their views and frames smth like:
- (void)transitionToChildViewController:(UIViewController *)toViewController {
UIViewController *fromViewController = ([self.childViewControllers count] > 0 ? self.childViewControllers[0] : nil);
if (toViewController == fromViewController || ![self isViewLoaded]) {
return;
}
UIView *toView = toViewController.view;
[toView setTranslatesAutoresizingMaskIntoConstraints:YES];
toView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
toView.frame = self.privateContainerView.bounds;
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[self.privateContainerView addSubview:toView];
[fromViewController.view removeFromSuperview];
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
}
the last method perhaps is the most tedious.

Can I set maximum alpha for CATransition?

Is it possible to adjust the maximum alpha (opacity) in a CATransition for when the views fade in and out?
What I want is something that looks like a modal segue. Compared to a default modal segue, the transition given by CATransition is very «dramatic».
Say I have two views.
A: the view I want to transition from.
B: the view I want to transition to.
I want B to come moving in from the bottom over A. For this I use the kCATransitionMoveIn type, with subtype kCATransitionFromTop (which is weird because B is moving up from bottom, not top).
In the default modal segue, this works fine, and A is only greyed out a little. With CATransition, A is totally black at towards the end of the transition when B has moved about 70% over A.
Code:
UIStoryboard *loginBoard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
UIViewController *vc = [loginBoard instantiateInitialViewController];
UIWindow *keyWindow = [[[UIApplication sharedApplication] delegate] window];
[keyWindow insertSubview:vc.view belowSubview:keyWindow.rootViewController.view];
CATransition *transition = [CATransition animation];
transition.duration = 0.5;
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromTop;
[keyWindow.layer addAnimation:transition forKey:kCATransition];
[keyWindow.rootViewController.view removeFromSuperview];
keyWindow.rootViewController = vc;
The problem originates from here.
After digging around some more I figured that you simply can't set a maximum opacity / alpha for the CATransition that you get out of the box.
So I solved it like this:
//offset the frame
vc.view.frame = CGRectMake(0, CGRectGetHeight(self.view.bounds), CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
[UIView animateWithDuration:5
animations:^{
//insert over/before root view instead of after
[keyWindow insertSubview:vc.view aboveSubview:keyWindow.rootViewController.view];
vc.view.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
keyWindow.rootViewController.view.alpha = 0.8;
}
completion:^(BOOL finished) {
NSLog(#"completed! now you can change the rootviewcontroller etc..");
}];

How to use animateWithDuration: when presenting view controller on iOS 7?

I'm currently using the following code in order to present my view controller.
CATransition* transition = [CATransition animation];
transition.duration = 1;
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromBottom;
[self.view.window.layer addAnimation:transition forKey:kCATransition];
[self presentViewController:viewController animated:NO completion:nil];
I need to use more complex animation utilizing UIView animateWithDuration method. It is just modal presentation and background view controller should stay there. How can I animate presenting view controller's view?
Update: next version of code :)
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:NULL];
RootViewControllerEx *viewController = (RootViewControllerEx *)[sb instantiateViewControllerWithIdentifier:#"SB_RDVC"];
viewController.transitioningDelegate = self;
viewController.modalPresentationStyle = UIModalPresentationCustom;
[self presentViewController:viewController animated:YES completion:nil];
...
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
...
}
VC in storyboard doesn't have any segue specified, it stays lonely in storyboard :)
You need to use UIModalPresentationCustom as your UIViewController's modalPresentationStyle.
You then need to create a class that conforms to the UIViewControllerTransitioningDelegate and set that your on presentedViewController.
Example
YourViewController *viewController = [[YourViewController alloc] init];
viewController.delegate = self;
viewController.transitioningDelegate = self;
viewController.modalPresentationStyle = UIModalPresentationCustom;
[self.navigationController presentViewController:viewController animated:YES completion:nil];
You must then implement the following method:
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
Which returns an object that conforms to a UIViewControllerAnimatedTransitioning protocol, that implements the following method:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *inView = [transitionContext containerView];
UIView *toView = [[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] view];
toView.frame = //CGRectMake(0,0,0,0);
[inView addSubview:toView];
// Use whatever animateWithDuration you need
[UIView animateWithDuration:0.6f delay:0.0f usingSpringWithDamping:0.7f initialSpringVelocity:0.5f options:UIViewAnimationOptionCurveEaseIn animations:^{
// Custom animation here
} completion:^(BOOL finished) {
// IMPORTANT
[transitionContext completeTransition:YES];
}];
}
Always remember to tell the context when a transition is complete, otherwise you'll enter an unstable state.

Present ViewController with custom animation

I am presenting a view controller using the foll. code:
VC_B * b = [[VC_B alloc] init...];
[self presentViewController:b animated:YES completion:nil];
I am trying to animate appearance of the view controller: the incoming VC should slide down from top covering VC that is currently displayed. following this solution makes it work with one problem: current VC disappears before the incoming VC appears on the screen. Is there a way to resolve this? perhaps there is an alternative solution to achieve this effect.
Try this code :
CreateNewViewController *newViewController = [[CreateNewViewController alloc]initWithNibName:#"CreateNewViewController" bundle:nil];
CATransition *transition = [CATransition animation];
transition.duration = 0.05;
transition.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type =kCATransitionMoveIn;
transition.subtype =kCATransitionFromTop;
transition.delegate = self;
[self presentModalViewController:newViewController animated:NO];
[self.view insertSubview:newViewController.view atIndex:0];
[self.view.layer addAnimation:transition forKey:nil];
Hope this will help you .
Try the below code. You just need to call below method from the point where you want to display NewViewController. What you need to do is that you just need to change the OriginY of NewViewcOntroller's View Frame.
-(void)displayNewVC
{
YourNewViewController *newVC = [[YourNewViewController alloc] init];
CGFloat originY = -450;//set originY as you want to display newViewController from the Top to bottom with animation
//initially set originY out of the Frame of CurrentViewcontroller
newVC.view.frame = CGRectMake(orginX, originY, newVC.view.frame.size.width, newVC.view.frame.size.height);
/*Display View With Animation*/
originY = 300;
[UIView animateWithDuration:.3 animations:^{
//set new originY as you want.
newVC.view.frame = CGRectMake(orginX, originY, newVC.view.frame.size.width, newVC.view.frame.size.height);
} completion:NULL];
[currentViewController.view addSubview:newVC.view];
}

Resources