My goal is to provide zooming modal transition from for the user from a view similar as springboard icons zoom in when launching apps.
The presented view controller zooms in correctly, but the navigation bar has wrong position under the status bar. This position gets corrected after calling [transitionContext completeTransition:finished];. How can I make it correct from the beginning of the transition?
This is a screen recording of the bug: http://youtu.be/7LKU4lzb-uw (the glitch is in the 6th second of the recording)
The UIViewControllerAnimatedTransitioning code:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGPoint viewCenter = self.view.center;
CGSize viewSize = self.view.frame.size;
CGSize controllerSize = toViewController.view.frame.size;
CGFloat controllerFromX = viewCenter.x - (controllerSize.width / 2);
CGFloat controllerFromY = viewCenter.y - (controllerSize.height / 2);
CGAffineTransform transform = CGAffineTransformMakeTranslation(controllerFromX, controllerFromY);
transform = CGAffineTransformScale(transform, viewSize.width / controllerSize.width, viewSize.height / controllerSize.height);
if (self.reverse) {
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
toViewController.view.transform = transform;
[container addSubview:toViewController.view];
}
[UIView animateKeyframesWithDuration:ZoomTransitioningDuration
delay:0
options:0
animations:^{
if (self.reverse) {
fromViewController.view.alpha = 0.0f;
fromViewController.view.transform = transform;
} else {
toViewController.view.transform = CGAffineTransformIdentity;
}
}
completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
The problem is that you are setting the transform before inserting the destination view controller's view into the container.
Switching the order should fix it:
if (self.reverse) {
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
[container addSubview:toViewController.view];
toViewController.view.transform = transform;
}
See point 4 here. Since you've applied a transform prior to inserting the navigation controller's view as a subview, the layout engine doesn't think the navigation bar is at the top edge of the window, and therefore doesn't need to be adjusted to avoid the status bar.
I've found a solution, although pretty hacky. I have to manually adjust the navigation bar frame before the animation starts:
if (self.reverse) {
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
toViewController.view.transform = transform;
[container addSubview:toViewController.view];
// fix navigation bar position to prevent jump when completeTransition: is called
if ([toViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*) toViewController;
UINavigationBar* bar = navigationController.navigationBar;
CGRect frame = bar.frame;
bar.frame = CGRectMake(frame.origin.x, frame.origin.y + 20.0f, frame.size.width, frame.size.height);
}
}
Add the toViewControllerView first to the ContainerView, then set the toViewControllerView transform as given below.
[container addSubview:toViewController.view];
toViewController.view.transform = transform;
This will solve the problem.
This is still a hack, based on Ondřej Mirtes' one but it works better if you have an in-call status bar and you're on iOS8
if([toViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navCtrl = (UINavigationController *)toViewController;
UINavigationBar *navBar = navCtrl.navigationBar;
if(navBar.frame.origin.y == 0 && navBar.frame.size.height == 44) {
navBar.frame = CGRectMake(0, 0, navBar.frame.size.width, fmin(44 + [UIApplication sharedApplication].statusBarFrame.size.height, 64));
}
}
Remains ugly though :/
Related
If user turn on the iOS 7 or above dynamic effect, when user taps an app icon, the system will have a zoom out/in effect for the app icon and its view, then go to start the app. I want to achieve similar zoom out/in animation. Any good implementations?
I got most code ready, like below, but not the exact animation I want:
#implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0;
UIApplication *app = [UIApplication sharedApplication];
MCAppDelegate *appDelegate = (MCAppDelegate *)app.delegate;
// CGRect fromFrame = appDelegate.UserTouchView.frame;
// CGRect toFrame = toViewController.view.frame;
//
CGFloat xFactor = fromViewController.view.frame.size.width / appDelegate.UserTouchView.frame.size.width;
CGFloat yFactor = fromViewController.view.frame.size.height / appDelegate.UserTouchView.frame.size.height;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
appDelegate.UserTouchView.transform = CGAffineTransformMakeScale(xFactor,yFactor);
toViewController.view.transform = CGAffineTransformMakeScale(1, 1);
toViewController.view.alpha = 1;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
I am new to animation, and trying to write a custom segue to achieve slide out a side menu from left to right, overlay the home view, and occupy half of the screen.
There is a custom segue code:
#import "CustomSegue.h"
#implementation CustomSegue
- (void)perform {
UIViewController *contentVC = self.sourceViewController;
UIViewController *sideBarVC = self.destinationViewController;
CGRect sideFrame = sideBarVC.view.frame;
[sideBarVC.view setFrame: CGRectMake(0, 0, 0, contentVC.view.frame.size.height)];
CGRect animationFrame = contentVC.view.frame;
sideFrame = sideBarVC.view.frame;
animationFrame.size.width = contentVC.view.frame.size.width / 2;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:
^{
sideBarVC.view.frame = animationFrame;
}
completion:
^(BOOL finished) {
sideBarVC.view.frame = animationFrame;
contentVC.view.alpha = 0.5f;
[[contentVC.view superview] addSubview:sideBarVC.view];
}];
}
#end
I add some variable to see if the frame is correct, and it is correct when I change the frame.
But I don't see my "expected" animation: sideBarVC.view.frame = animationFrame;
Could some one help on this? Thanks.
If you want to have a slide from left to right animation, you will firstly have to set the original X of animating view's frame to be a negative value, then in the animation block, set original x to be 0. Something like that.
sideFrame = sideBarVC.view.frame;
sideBarVC.view.frame = CGRectOffset(sideFrame, - contentVC.view.frame.size.width, 0);
[[contentVC.view superview] addSubview:sideBarVC.view];
[UIView animateWithDuration:0.3 animations:^{
sideBarVC.view.frame = sideFrame;
contentVC.view.alpha = 0.5f;
} completion:nil];
Using your code I don't see animation happens, because sideBarVC.view is added in the completion block of the animation. By then the animation has already finished, and sideBarVC.view is added as subview and its frame is set.
Tunred out I misunderstand the position. Using below code could achieve the animation,
but it still has one problem, the side bar menu is under navigation tool bar on top of the screen. Do anyone know how to solve it?
- (void)perform {
UIViewController *contentVC = self.sourceViewController;
UIViewController *sideBarVC = self.destinationViewController;
CGRect sideFrame = sideBarVC.view.frame;
[sideBarVC.view setFrame: CGRectMake(-(contentVC.view.frame.size.width), 0, contentVC.view.frame.size.width/2, contentVC.view.frame.size.height)];
CGRect animationFrame = contentVC.view.frame;
sideFrame = sideBarVC.view.frame;
animationFrame.size.width = contentVC.view.frame.size.width / 2;
sideBarVC.view.alpha = 0;
[[[contentVC.view superview] window] addSubview:sideBarVC.view]; < -- this is important, not [superview addSubView]
[UIView animateWithDuration:1
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
sideBarVC.view.frame = animationFrame;
sideBarVC.view.alpha = 1;
}
completion:^(BOOL finished) {
sideBarVC.view.alpha = 1;
sideBarVC.view.frame = animationFrame;
}];
For Swift 3
override func perform() {
let contentVC = self.sourceViewController;
let sideBarVC = self.destinationViewController;
var sideFrame = sideBarVC.view.frame
sideBarVC.view.frame = CGRect(x: -(contentVC.view.frame.size.width), y: 0, width: contentVC.view.frame.size.width/2, height: contentVC.view.frame.size.height)
sideFrame = sideFrame.offsetBy(dx: -1 * contentVC.view.frame.size.width, dy: 0)
contentVC.view.superview?.addSubview(sideBarVC.view)
UIView.animate(withDuration: 0.3) {
sideBarVC.view.frame = sideFrame
contentVC.view.alpha = 0.5
}
}
I want to display a scaled down "preview" of another ViewController in another ViewController. Is this possible? No interaction are needed so it can basically be a screenshot, but scaled down. I can be solved by manually take a screenshot and save it as an image, but the ViewController contains a lot of localizations and are constantly updated, so if this can be done programatically.
https://www.dropbox.com/s/kd5vinrym1k7afk/Screenshot%202014-05-13%2015.20.17.png
Is this possible?
Edit: the sub view is stored in a storyboard
Edit: This is how I've solved it:
//Load the viewcontroller and view
previewViewController = [self.storyboard instantiateViewControllerWithIdentifier:[PLACEHOLDER_VIEWCONTROLLER_NAMES objectAtIndex:self.identifier]];
[self addChildViewController:previewViewController];
[self.placeholderView.superview addSubview:previewViewController.view];
previewViewController.view.transform = CGAffineTransformMakeScale(scale*2, scale*2);
CGPoint center = self.placeholderView.center;
center.y += 25;
previewViewController.view.center = center;
Use the viewController.view and then scale it down like:
viewController.view.transform = CGAffineTransformMakeScale(0.5, 0.5);
Add your another view controller view as subview to first viewcontroller
[UIView animateWithDuration:2.0f animations:^{
InnerView.userInteractionEnabled = NO;
//Always starts from original scale
InnerView.transform = CGAffineTransformMakeScale(0.5, 0.5);
//Incremental scale
//InnerView.transform = CGAffineTransformScale(InnerView.transform, 0.5, 0.5);
} completion:^(BOOL finished) {
NSLog(#"in Half size");
}];
[UIView animateWithDuration:2.0f animations:^{
InnerView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
InnerView.userInteractionEnabled = YES;
NSLog(#"Normal size");
}];
Using Xamarin.iOS to develop an iPhone app.
I would like to achieve the same kind of animation as in the iPhone iOS7 Home Screen, where you zoom in to an app. I have a set of pictures in a Collection view. Each picture corresponds to a person, where you can tap the pic, to get to the next view controller, where you get information about the person .
Right now I am animating from bottom to top - would it be possible to achieve my desired animation type?
public override void ItemSelected (UICollectionView collectionView, NSIndexPath indexPath)
{
UIStoryboard MainStoryboard = UIStoryboard.FromName ("MainStoryboard_iPhone",null);
PersonDetailViewController personDetailViewController = MainStoryboard.InstantiateViewController ("PersonDetailViewController") as PersonDetailViewController;
personDetailViewController.personID = _viewController.Category.Persons [indexPath.Row].Id;
this._viewController.NavigationController.PushViewController (personDetailViewController,false);
var animation = CABasicAnimation.FromKeyPath("transform.translation.y");
animation.Duration = 0.3f;
animation.From = NSNumber.FromFloat(this._viewController.View.Frame.Height);
animation.To = NSNumber.FromFloat(0f);
this._viewController.NavigationController.View.Layer.AddAnimation(animation, "animate");
this._viewController.NavigationController.View.Layer.AnimationForKey("animate");
}
Check out this sample at objc.io of a zooming transition - hopefully you can translate from the Objective-C. Here's the animation part:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
toViewController.view.alpha = 1;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
Hi maybe this is a duplicated question and I just don't know the keyword to describe my question, if this is the case please point out.
I'm currently implementing a transition between two view, as the second view shows, it should looked like it's zoomed into the screen from outside, kind of fall on the screen. My idea is to zoom the second view 9 * original size and place the origin to -original_width, -original_height, and set alpha to 0. At the end I set alpha to 1 and show the original size.
so the following code is not working, the subviews are not resized. Could anyone tell me what I miss here or better show me some API which do the task?
[toVC.view setFrame:CGRectMake(0-fromVC.view.frame.size.width, 0-screenRect.size.height, fromVC.view.frame.size.width*3, fromVC.view.frame.size.height*3)];
for (UIView* view in toVC.view.subviews) {
CGRect frame = view.frame;
[view setFrame:CGRectMake(frame.origin.x*3, frame.origin.y*3, frame.size.width*3, frame.size.height*3)];
}
[toVC.view layoutIfNeeded];
[toVC.view setAlpha:0.1];
[UIView animateWithDuration:duration*5
animations:^{
[toVC.view setAlpha:1.0];
[toVC.view setFrame:CGRectMake(0, 0, fromVC.view.frame.size.width, fromVC.view.frame.size.height)];
for (UIView* view in toVC.view.subviews) {
CGRect frame = view.frame;
[view setFrame:CGRectMake(frame.origin.x/3, frame.origin.y/3, frame.size.width/3, frame.size.height/3)];
}
}
completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
You could just set the view transform prior and after the transition:
CGAffineTransform scaleTransform = CGAffineTransformIdentity;
scaleTransform = CGAffineTransformScale(rotationTransform, 9., 9.);
view.transform = scaleTransform;
view.alpha = 0;
[UIView animateWithDuration:duration*5
animations:^{
view.transform = CGAffineTransformIdentity;
view.alpha = 1.;
}
}
completion:^(BOOL finished) {
}];