I have an NSPopover, which switches between two View Controllers.
I set one at launch,
self.popover.contentViewController = viewController1;
and I change to the other one based on a user action:
self.popover.contentViewController = viewController2;
AppKit does some animations to the NSPopover itself to accomodate the second VC, which work well, but the transition between the two View Controller’s views is jarring. I would like them to slide in and out horizontally or at the very least cross fade.
I am using layer-backed views, which I'm setting like this in the AppDelegate’s applicationDidFinishLaunching method:
self.popover.contentViewController.view.wantsLayer = YES;
self.popover.contentViewController.view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
Here’s what I’ve tried:
fading out the first View Controller's view in -viewWillDisappear and fading in the second View Controller's view in -viewWillAppear. This fades out the first VC’s view immediately and does not fade the second VC’s view back in
fading out the first VC’s view’s layer before switching the popover’s contentViewController:
CALayer *theLayer = self.popover.contentViewController.view.layer;
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:#"opacity"];
theLayer.opacity = 0.0;
self.popover.contentViewController = self.viewController;
This second strategy doesn't wait for the animation to finish before switching the View Controllers over.
How do I animate changing the NSPopover’s contentViewController?
So since you have multiple child view controllers in a parent view controller you should follow the view controller containment protocol. Rather than animating the views you want to animate the transition between the view controllers themselves.
toController.view.frame = fromController.view.bounds;
[containerViewController addChildViewController:toController];
[fromController willMoveToParentViewController:nil];
[containerViewController transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.2
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:^(BOOL finished) {
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:containerViewController];
toViewController.view.frame = containerViewController.view.bounds;
}];
Where containerViewController is your contentViewController.
Pretty much just tell the old view controller it's being removed from the parent, animate the transition, and remove the old controller and let the new controller know it's been moved to the parent.
More info from apple here: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
And good tutorial on custom transitions here: http://www.objc.io/issues/12-animations/custom-container-view-controller-transitions/
Related
I have a view controller that on certain action presents another view controller that covers bottom half of the screen.
When I animate the second view controller (presented view controller) from the bottom to cover the bottom half of the screen, I also want animate the presenting view to top half of the screen. See the code below -
- (void)animatePresentationWithTransitioningContext:(id<UIViewControllerContextTransitioning>) transitioningContext
{
UIViewController *presentedController = [transitioningContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *presentedControllerView = [transitioningContext viewForKey:UITransitionContextToViewKey];
UIView *containerView = [transitioningContext containerView];
presentedControllerView.frame = [transitioningContext finalFrameForViewController:presentedController];
CGPoint center = presentedControllerView.center;
center.y = containerView.bounds.size.height;
presentedControllerView.center = center;
[containerView addSubview:presentedControllerView];
// This returns nil
UIView *presentingControllerView = [transitioningContext viewForKey:UITransitionContextFromViewKey];
[UIView animateWithDuration:[self transitionDuration:transitioningContext] delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
CGPoint presentedViewCenter = presentedControllerView.center;
presentedViewCenter.y -= containerView.bounds.size.height/2.0;
presentedControllerView.center = presentedViewCenter;
// This is where I want to move the presenting view controller.
// of course it does not work.
CGPoint presentingViewCenter = presentingControllerView.center;
presentingViewCenter.y -= containerView.bounds.size.height/2.0;
presentingControllerView.center = presentingViewCenter;
} completion:^(BOOL finished) {
[transitioningContext completeTransition:finished];
}];
}
The viewForKey:UITransitionContextFromViewKey returns nil. I overrode shouldRemovePresentersView to return NO in my custom UIPresentationController but I still get nil. I am using UIModalPresentationCustom as the modal presentation style for the view that I am presenting.
EDIT:
I realized that I can achieve the same affect by animating the presenting view by overriding the presentationTransitionWillBegin method in my custom UIPresentationController. But I would like to get an answer on accessing the presenting view controller.
EDIT:
Since I have found a solution, clarifying my question: (1) Is this the expected behavior i.e. the presentingViewController is going to be nil in the animator object?
I am using UIModalPresentationCustom as the modal presentation style for the view that I am presenting.
Well, there's your problem. In that case, the From view will be nil. It is nil unless you are using the FullScreen presentation style.
I'm not saying you are wrong to make the presentation style Custom. You need to do that, if you want to leave the presenting view controller's view in place, and if you want to supply your own presentation controller. And you do! But in that case, the work is divided: it is the presentation controller that becomes responsible for the final position of the view.
By the way, it occurs to me (thinking about it some more) that you are doing something you should probably not be doing. You are not expected to move the presenting view controller's view. It might be safer to take a snapshot view of the presenting view controller's view and animate that, behind your presented view controller's view.
I am creating my own slide menu which is pretty simple.
I am creating a subview, adding it to the view and then sliding the self.view to the right by 170px which reveals the new subview (named secondView).
However, none of the buttons on the new view are clickable after moving the self.view to the right (as they were before I did this). So I image that because the view has moved, the view is the clickable region of the app.
Is there a way to move the content across as opposed to the view to allow the new view to be clickable.
The problem is:
Touches outside of a view's frame never trigger events to its subviews.
I mean, touches outside of self.view.frame never trigger event to secondView that is one of subviews of self.view.
see: Event Handling Guide for iOS
You can [self.view.window addSubview:secondView] and move both self.view and secondView.
[self.view.window addSubView:secondView];
CGRect viewFrame = self.view.frame;
CGRect secondFrame = secondView.frame;
viewFrame.origin.x += 170;
secondFrame.origin.x += 170;
[UIView animateWithDuration:0.2 animations:^{
self.view.frame = viewFrame;
secondView.frame = secondViewFrame;
}];
Note:
Above code may cause troubles if self is not window's rootViewController. e.g. in UINavigationController.
I'm adding an MPMoviePlayerController to a container view like so:
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:self.videoURL];
. . .
self.moviePlayer.view.frame = self.frame;
[self addSubview:self.moviePlayer.view];
The self is the container view. This container view is instantiated off-screen when it is created by a view controller. So the container view resides in a view, and the MPMoviePlayerController resides in the container view.
When I animate the container view on screen, it works. Other views slide off and this view slides in. However, the MPMoviePlayerController stays put with its frame off-screen. So the MPMoviePlayerController's container view moves on screen, but it does not move with its container view. Is this not allowed, or am I missing something? The code for animating the container view is below:
[UIView animateWithDuration:ANIMATION_LENGTH animations:^() {
for(int i = beginTag; i < beginTag + [self subviews].count; i++) {
UIView *viewPointer = [self viewWithTag:i];
CGRect viewFrame = viewPointer.frame;
viewFrame.origin.x -= SCREEN_WIDTH_VC;
viewPointer.frame = viewFrame;
}
} completion:^(BOOL finished) {
[self.vidLoad playVideo];
}];
This takes care of animating elements that are on-screen off of the screen, and then bringing off-screen elements onto the screen. I'm glad to hear all suggestions!
Solved - this was a result of my own stupidity. The view hierarchy was indeed working, but when I assigned self.frame to moviePlayer.view.frame, it took the absolute coordinates of the container view, not relative coordinates.
A silly mistake. Basically, I just had to write
self.moviePlayer.view.frame = CGRectMake(0, 0, SCREEN_WIDTH_VC, SCREEN_HEIGHT_VC);
This would place it right on top of the container view, as these coordinates are relative to that container view and not the screen (I just confused myself).
This is how I switch from one UIView to another UIView: self.view = MyView;
How would I fade out a view to my MySecondView?
You can use UIView's build in transition code to cross dissolve between them.
[UIView transitionFromView:self.view toView:MySecondView duration:0.25 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
// What to do when its finished.
}];
You can also use the regular animation options with something like this. I haven't tested this code as I'm not in front of Xcode but I use it all the time for labels and things without default animations.
[UIView animateWithDuration:0.25 animations:^{
[self.view setAlpha:0.0];
self.view = mySecondView;
[self.view setAlpha:1.0];
}];
Please note that it's not a good idea to do things like self.view = MyView to change screens. By the time you get to a few screens, your viewController will be filled with spaghetti code. You should consider presenting new view controllers that manage their views. One way you can do fade is as follows:
Fade the current view to black (with animation)
In the view controller that you are going to push use viewWillAppear to fade the view to black as well
Push/Present the view controller without animations.
Now use the viewDidAppear method of the newly presented view controller to fade in the view (with animation).
I have an UIView and its subview. When I animate myView (size.x+20) my subview is being animated too, however I want to translate my subview at the same time independently(origin.x-40) (without the subview moving because of the resizing of myView).
I was able to do it by adjusting the translations (or better position, because I might wanna change y later) .fromValue, .toValue and .duration to compensate movement caused by changes to myView.
UIView beginAnimations:nil context:NULL];
[myView setFrame:pVnewFrame];
[UIView commitAnimations];
CABasicAnimation *bBTranslateAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
bBTranslateAnimation.delegate = self;
bBTranslateAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(160, 480)];
bBTranslateAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(160, 436)];
bBTranslateAnimation.duration = 0.35;
[buttonBar addAnimation:bBTranslateAnimation forKey:#"anim"];
But that is no proper solution. Is there a good way to move view and subviews independently?
(setting autoresizeSubviews and autoresizingMask doesn't help)
Thanks in advance!
The way to do this is to make the two views siblings, instead of making one view a subview of the other. Just put the view that was the subview on top of the view that was the parent view, and it should look like you want it to.
If you want the "child" view to clip to the contents of the former parent view, you might need to put the view that was the child inside a container view that is a sibling of the view that was the parent, and set the container view to opaque = no and with a clear background color. That will affect performance, however.