In iOS 7, with customer VC transitioning, is it possible to make "fromviewcontroller" on screen after presentViewController? - ios

Many apps have the feature which, user can pull down one view, and another view shows up from underneath it. But the first view is still visible on the bottom. (e.g. Facebook Paper App).
If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
Anyone more familiar with this ? Thanks !

If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
Yes, it's possible. You can use the snapshot API to take a snapshot of any UIView. With this you can take a snapshot of view controller you're transitioning from, then add it to the containerView below the view controller you're transitioning to.
For example, in the -animateTransition: method of your class that adopts UIViewControllerAnimatedTransitioning take a snapshot of the view controller you're transitioning from and add it as a subview beneath the view controller you're transitioning to:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView * containerView = transitionContext.containerView;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// take snapshot of from view controller
UIView * fromSnapshotView = [fromViewController.view snapshotViewAfterScreenUpdates:NO];
[containerView insertSubview:fromSnapshotView belowSubview:toViewController.view];
// Then do your animations on the to view controller to animate it into view as well as the fromSnapshotView
// Finally, don't forget to call:
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
If your call to dismiss doesn't fire your transitioning delegate methods make sure that you set the transitioning delegate on that view controller before dismissing.

Related

Creating custom push segue

I'm writing a custom segue class (HySegue) which allows views to animate the transition. The code is working great, except when under a UINavigationController stack. Actually, the transitions run and animate well, but the top UINavigationBar is what is causing me problems.
When first animating, I add the destination view as a subview of the source view. As such:
UIView * sourceView = sourceViewController.view;
UIView * destinationView = viewControllerToPresent.view;
// Force the source view to layout
[sourceView addSubview:destinationView];
[sourceView layoutIfNeeded];
When the transition is over, I present the destination view controller:
UIViewController * parentViewController = viewControllerToDismiss.parentViewController;
UIView * destinationView = destinationViewController.view;
// Break the view hierarchy that was setup earlier
[destinationView removeFromSuperview];
// When presenting in a UINavigationController stack, push the destination view controller
if ([parentViewController isKindOfClass:[UINavigationController class]]) {
[(UINavigationController *)parentViewController pushViewController:destinationViewController
animated:NO];
}
else {
// Present the destination view controller
[viewControllerToDismiss presentViewController:destinationViewController
animated:NO
completion:nil];
[viewControllerToDismiss willMoveToParentViewController:nil];
[viewControllerToDismiss.view removeFromSuperview];
[viewControllerToDismiss removeFromParentViewController];
[viewControllerToDismiss didMoveToParentViewController:nil];
}
The problem is that during the animation the destination view does not know that it's being pushed in a navigation stack and so the top bar, although visible, is not taken into account in the view's bounds. That is, the navigation bar is visible because it's visible for the source view controller, but the destination view controller knows nothing about it. When the animation finishes the destination view controller is pushed onto the stack, so it now knows about the navigation bar, and all my content with Top Space to Top Layout Guides constraints jumps down on the frame.
The top bar also doesn't show in IB. This is when I use my custom segue:
And this is when I use show segue:
Notice that the bar shows on the later but not on the former.
What I was wondering is how IB knows that it's a push segue. Is it some flag? Is it because of the specific class it's using? How would I make my custom segue also a push segue?
Edit: I loved the segue's type in prepareForSegue:sender: and it seems its type is UIStoryboardPushSegue, which is not a public or documented class. How can I can I solve it then? I already tried setting the destination view's frame and bounds from the source view.

Animating the presenting view in a UIPresentationController

For some context, I recommend reading this:
Very relevant question: "From View Controller" disappears using UIViewControllerContextTransitioning
Very relevant answer: https://stackoverflow.com/a/25901154/751268
I'm trying to implement a custom view controller transition that animates the new view controller to cover half the screen, while simultaneously shrinking the presenting view controller to 90% (centered in the window, underneath the presented view controller).
First, my problem was that viewFromKey: returned nil. To solve that, the answer mentioned:
If you want to animate the presenting view controllers's view you should consider using UIModalPresentationFullscreen style or continue using UIModalPresentationCustom and implement your own subclass of UIPresentationController with shouldRemovePresentersView returning YES.
I did that, and viewFromKey: doesn't return nil anymore, but now the presenting view controller disappears completely (which makes sense considering I explicitly say it should by implementing shouldRemovePresentersView).
I add the presenting view controller's view to the container view, but it still gets removed. Is there anything else I should be doing to get this working?
Here's some relevant code:
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
BOOL show = self.isPresentation;
UIView *menuView = show ? toView : fromView;
UIView *backView = show ? fromView : toView;
UIView *containerView = [transitionContext containerView];
[containerView addSubview:backView];
[containerView addSubview:dimmedView];
[containerView addSubview:menuView];
// Adjust transforms, alpha and perform animations
I thought that by returning YES from shouldRemovePresentersView and manually adding it to the containerView, that should fix the issue, but backView gets removed anyway...
I'm adding another answer, as my response is too long to fit in a comment.
First of all the viewForKey is available in iOS8, so unless you are targeting iOS8 only (why?) you should not use it, or use it after checking that the UIViewControllerContextTransitioning responds to that selector and use the viewControllerForKey for iOS7.
With that being said, it seems to me that this is a bug and I explain my self:
If you look at the UIPresentationController header file, you will see that it says
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the
// presentation transition
// (Default: YES)
- (BOOL)shouldRemovePresentersView;
So as you see the default is YES, so this should only overriden when you specifically want to say NO.
However, you are right, without this being set explicitly to YES, the viewForKey for the UITransitionContextFromViewControllerKey remains nil.
I think you should fill in a bug report for this and for now use the viewControllerForKey which is fine to be used (nothing wrong with this) as it's not deprecated and works in both OS versions without a problem.
And the reason this is most likely a bug is that the viewForKey should return a view for theUITransitionContextFromViewControllerKey when the shouldRemovePresentersView is explicitly set to NO and not YES.
My 2 cents
Why are you setting shouldRemovePresentersView to YES if what you want is that the fromView remains visible?
I had the same problem as you, as in my custom 3d presentation the parent view controller was removed as soon as the transition was completed.
The solution there is to change the modalPresentationStyle to UIModalPresentationOverCurrentContext
This will specifically prohibit the system to remove the owner viewController when the transition ends.
Still my 3D transition suffered from animation issues, when using this approach.
Si I ended up using a UIModalPresentationCustom modalPresentationStyle which solved almost all my issues, with the exception that the new view controller (which was a UINavigationController) would not move down when the in-call status bar would appear.
To solve this, I ended up changing the modalPresentationStyle back to UIModalPresentationFullScreen after the transition had been completed.
Here is my code that shows the new viewController with the custom presentation:
//show Login Screen
LoginViewController *viewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *loginNavController = [[UINavigationController alloc] initWithRootViewController:viewController];
loginNavController.interactivePopGestureRecognizer.enabled = YES;
loginNavController.transitioningDelegate = self.customTransitionDelegate;
loginNavController.modalPresentationStyle = UIModalPresentationCustom;
[navigationController presentViewController:loginNavController animated:YES
completion:^{
//important, else status bar is not moving entire screen down....
loginNavController.modalPresentationStyle = UIModalPresentationFullScreen;
}
];
Also very important is that you must NOT add the old view to the containerView when you run your dismissal animation
So if the animation is the presenting, add your toView to the containerView
UIView* inView = [transitionContext containerView];
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
.....
......
[inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
But on the dismissal presentation DO NOT ADD the view to the containerView as it is still showing (since we specifically asked the system NOT to remove it), but simply animate between the two views.

Custom presented UIViewController changing to fullscreen

I have a view controller presented using UIModalPresentationCustom presentation style. I use a custom UIViewControllerTransitioningDelegate to present the view controller as a sidebar (so it slides in from the edge of the screen and does not occupy the full screen).
However when I then present another view controller from this one using UIModalPresentationFullScreen — and then dismiss the full screen view controller, my underlying custom presented controller is suddenly resized to occupy the full screen. Does anyone know why this is the case?
Edit: this is essentially my animateTransition method for presenting the sidebar — I've stripped out most of the code to make it readable. Basically it gets the container from the transitionContext, adds and animates the destination view controller's view to the container.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = transitionContext.containerView;
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
if( toVC.isBeingPresented )
{
[container addSubview:toView];
//... Animate some new frame for toView
//Call [transitionContext completeTransition:YES] on animation completion
}
else
{
//... Animate fromView out
//On completion remove fromView from superview
//Call [transitionContext completeTransition:YES] on animation completion
}
}
Edit 2: Doing a little more research, I notice that the frame of my custom presented view controller's view is being set when the view controller above it in the modal stack is dismissed. The following stack trace leads to the frame being set as full screen:
0 -[MyCustomPresentedViewControllerView setFrame:]
1 -[UIView(MPAdditions) setFrameOrigin:]
2 -[UIViewControllerAccessibility(SafeCategory) dismissViewControllerWithTransition:completion:]
3 -[UIViewController dismissViewControllerAnimated:completion:]
Changing the presentation style by assigning:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
results in the modal presentation controller occupying a fraction of the screen, the same fraction as the original presented view controller of the UIPresentationController.
However, using viewController.modalPresentationStyle = UIModalPresentationOverFullScreen
is better for this case, since his modal controller is full screen.
I am experiencing the same issue. I've tried debugging it using symbolic breakpoints and there seem to be some internal call on some kind of layout manager that does this.
While I wasn't able to "solve" this (it seems to me like a bug in the SDK), I was able to come up with a workaround that fixes this. Basically, you have to set the correct dimensions of the presented view at two opportune times. Like this:
In the view controller that was presented using your custom UIPresentationController, add this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
});
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
}
And if you are wondering: yes, you do need it to do in two places. Because weirdly enough, when I did it only in the viewDidAppear async block, it got broken (resized to fullscreen) once the animation finished.
Try to use:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
It helped me in iOS 8.1

Use destinationViewController button in a custom segue

My goal is to make a custom segue with a custom animation as follows:
I want the segue to cover a button from the sourceViewController with a button from the destinationViewController with an effect similar to the navigation controller's push effect, i.e. the new button is supposed to push the old button away from right to left.
I have been able to make the old button (from the sourceViewController) move away as desired:
[UIView animateWithDuration:1.0 animations:^{
// set the target frame for animated view
sourceViewController.optionsButton.frame = leftButtonTargetFrame;
} completion:^(BOOL finished) {
[navigationController pushViewController:destinationViewController animated:NO];
// reset the button's frame back to its original frame
sourceViewController.optionsButton.frame = leftButtonInitFrame;
}];
But I am struggling to make the new button (from the destinationViewController) move in. The reason is that I cannot access the destinationViewController's view elements: While performing the segue they are not instantiated. And I cannot animate a button that is not instantiated.
So how can I replace a button in the sourceViewController with a button from the destinationViewController?
The view of the destination view controller hasn't been initialized/loaded at the time when you try to access the buttons. To load the view of the destination view controller, you can simply access the view property. Do this before using the buttons: [destinationViewController view];
destinationViewController.view; would also work, but it would generate a compiler warning.
Background Information:
If you access the view property and its value is currently nil, the view controller automatically calls the loadView method and returns the resulting view.
The method loadView loads the view that the controller manages. You should never call this method directly.
You are correct that you cannot animate an object that does not yet exist. However, you can fake it.
Create a place-holder button that will look identical to the button that will be in the new view controller.
Animate it to the correct place.
As the destination view controller comes in, its button should be invisible.
After the the view controller is in place (i.e. the segue has finished) the destination view controller can ensure the proper placement if its button and make its actual button visible.
Hope this helps.

Source view controller hides during custom segue

I'm trying to make a custom segue so that the destination view controller slides down from the top.
I wrote my code according to the example from the documentation.
The problem is that when the segue is executed, the source view controller goes black, and then the animation occurs. How can I prevent the source view controller from going black?
(I already tried implementing the solution presented in this answer but the screen either goes black after the transition, or reverts to the source view controller.)
Here's my code:
-(void)perform{
UIViewController *splashScreen = self.sourceViewController;
UIViewController *mainScreen = self.destinationViewController;
//Place the destination VC above the visible area
mainScreen.view.center = CGPointMake(mainScreen.view.center.x,
mainScreen.view.center.y-600);
//Animation to move the VC down into the visible area
[UIView animateWithDuration:1
animations:^{
mainScreen.view.center = CGPointMake(mainScreen.view.center.x, [[UIScreen mainScreen] bounds].size.height/2 );
}
];
[splashScreen presentModalViewController:mainScreen animated:NO];
}
The reason that your source view controller seems to be hidden is that the destination view controller is presented right away.
When you are writing custom segues you don't have both views available at the same time. You could either
push view controller, add the source view to the destination view controller and animate
add the destination view to the source view controller and animate, then push view controller
push to an in-between view controller, add both views, animate, push to the destination view controller.
In all the above cases where I say push view controllers you could instead present view controllers modally. In fact that might be more suitable for the in-between view controller solution.

Resources