In order to use a UISplitViewController, I'm replacing my window root controller when navigating from one view controller to the other.
In order to have some nice transition while doing so, I'm using a zooming effect like this:
MyOtherViewController *controller = [[MyOtherViewController alloc] initWithNibName:#"MyOtherView" bundle:nil];
UIWindow *window = ((MyAppDelegate *)[[UIApplication sharedApplication] delegate]).window;
controller.view.frame = [window frame];
controller.view.transform = CGAffineTransformMakeScale(0.01,0.01);
controller.view.alpha = 0;
[window addSubview:controller.view];
[UIView animateWithDuration:0.2 animations:^{
controller.view.transform = CGAffineTransformMakeScale(1,1);
controller.view.alpha = 1.0;
} completion:^(BOOL finished) {
if (finished) {
[self.view removeFromSuperview];
window.rootViewController = controller;
}
}];
and this works pretty well, except that while doing the animation, the new view is always oriented as if in portrait mode, regardless of the current device orientation. When the animation is finished, the view orients itself correctly.
What am I missing?
Things I've tried:
putting my new controller view as the sole subview of the UIWindow
making my new controller the root view controller before the animation begins
A curious thing is that, if I do a recursiveDescription on the window at the beginning of my method, the window frame is defined as having a size of 768x1024 (i.e., portrait), and the view inside it of 748x1024 but with a transform of [0, -1, 1, 0, 0, 0] (does this mean a rotation or what? Shouldn't it be the identity transform?)
UIWindow doesn't rotate. It has a rotated view inside of it (as you've seen). In this case, though, I think the problem is likely that your view has a transform on it already at this point, and you need to concatenate with it rather than replace it as you're doing in your setTransform: calls.
You shouldn't be asking the app delegate for the window, you should be getting the window from the view (self.view.window).
If at any point you're attaching your view to the window itself, rather than putting it inside the rotation view, then you'll need to know the effective transform of the view you want to match by walking the hierarchy:
- (CGAffineTransform)effectiveTransform {
CGAffineTransform transform = [self transform];
UIView *view = [self superview];
while (view) {
transform = CGAffineTransformConcat(transform, [view transform]);
view = [view superview];
}
return transform;
}
I finally figured out what was wrong. Since the frame is not a real property but a sort of calculated one, based on the values of the view bounds and the view transform, I needed to set the frame after setting the same transform as the current view, and before setting the transform again to set up the initial state of the animation. Also, the frame I need to set is the same one as the current view is currently using, as it is taking into account the window orientation (or lack thereof, as Rob Napier pointed)
So, without further ado, here's the working code:
MyOtherViewController *controller = [[MyOtherViewController alloc] initWithNibName:#"MyOtherView" bundle:nil];
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGAffineTransform t = self.view.transform;
controller.view.transform = t;
controller.view.frame = self.view.frame;
controller.view.transform = CGAffineTransformScale(t,.01,.01);;
[window addSubview:controller.view];
controller.view.alpha = 0;
[UIView animateWithDuration:0.2 animations:^{
controller.view.transform = t;
controller.view.alpha = 1.0;
} completion:^(BOOL finished) {
if (finished) {
[self.view removeFromSuperview];
window.rootViewController = controller;
[controller release];
}
}];
Related
I've got a custom UIStoryboardSegue subclass which just replaces the root view controller with the destination VC. Works exactly as I want it to... however, I'd like to be able to add a transition animation, and I can't find any good examples of how to do that in the context of replacing the root VC.
The -perform selector of my class is this:
-(void)perform {
UIViewController *source = (UIViewController *)self.sourceViewController;
source.view.window.rootViewController = self.destinationViewController;
}
...How do I add a nice animated transition?
There are numerous ways you could add a "nice animation". Here is an example of a sort of card shuffle animation where one view moves up and left, while the other moves down and right, then reverses after changing the z-order of the two views. This implementation inserts the destination controller's view into the window's view hierarchy under the source controller's view.
-(void)perform {
CGFloat dur = 1.0;
UIView *destView = [self.destinationViewController view];
UIView *srcView = [self.sourceViewController view];
CGFloat viewWidth = srcView.frame.size.width;
CGPoint center = srcView.center;
AppDelegate *appDel = [[UIApplication sharedApplication] delegate];
destView.frame = srcView.bounds;
[appDel.window insertSubview:destView belowSubview:srcView];
[UIView animateWithDuration:dur animations:^{
srcView.frame = CGRectOffset(srcView.frame, -viewWidth/1.9, -20);
destView.frame = CGRectOffset(destView.frame, viewWidth/1.9, 20);
} completion:^(BOOL finished) {
[appDel.window bringSubviewToFront:destView];
[UIView animateWithDuration:dur animations:^{
destView.center = center;
srcView.center = center;
} completion:^(BOOL finished) {
[srcView removeFromSuperview];
appDel.window.rootViewController = self.destinationViewController;
}];
}];
}
I'm trying to create a custom transition to a new view controller using a UIStoryboardSegue subclass, but I'm having some issues. I have a back button that's setup to with AutoLayout to be 5 pixels from the top layout guide (essentially 25 pixels from the top of the view). However, when I'm transiting my view doesn't take into account that the status bar exists and makes the button 5 pixels away from the top of the view instead of 25 pixels away from the top of view (status-bar.height == 20px). So when the transition finishes theres a sudden bounce when I call [[self sourceViewController] presentModalViewController:[self destinationViewController] animated:NO]; because then the button will actually be layer out correctly. Does anyone have any ideas how I can tell my view that it should layout assuming that the top layout guide starts at 20px instead of 0?
Edit: The issues seems to be directly related to the topLayoutGuide. When checking the value of topLayoutGuide it was 0 when I added the view in the transition, but when I checked it in the destination view controller's viewDidAppear it was 20 like it was supposed to be.
- (void)perform {
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
UIViewController *sourceViewController = (UIViewController*)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController*)self.destinationViewController;
[destinationViewController.view setCenter:CGPointMake(window.frame.size.width * 1.5, sourceViewController.view.center.y)];
[[sourceViewController.view superview] addSubview:destinationViewController.view];
[destinationViewController.view setBackgroundColor:[UIColor redColor]];
POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
positionAnimation.toValue = #(window.center.x);
positionAnimation.springBounciness = 10;
POPSpringAnimation *fromPositionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
double offScreenX = -sourceViewController.view.frame.size.width;
fromPositionAnimation.toValue = #(offScreenX);
fromPositionAnimation.springBounciness = 10;
[fromPositionAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
}];
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.springBounciness = 20;
scaleAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.1)];
[scaleAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
[[self sourceViewController] presentModalViewController:[self destinationViewController] animated:NO];
}];
[sourceViewController.view.layer pop_addAnimation:fromPositionAnimation forKey:#"positionAnimation"];
[destinationViewController.view.layer pop_addAnimation:positionAnimation forKey:#"positionAnimation"];
[destinationViewController.view.layer pop_addAnimation:scaleAnimation forKey:#"scaleAnimation"];
}
I suggest call setNeedLayout on viewWillAppear. It's should adjust the layout of a view’s subviews.
OK, I get the concept of adding a UIImageView as soon as the app launches, and animating it out to fake the splash animation. However, the status bar interferes.
I need a UIImageView on top of everything, including the status bar, and while it fades away, the app is shown with the status bar. Hence, settings the Status bar initially hidden, then animating it in is not a viable option.
What you require is a second UIWindow with a windowLevel of UIWindowLevelStatusBar or higher. You would create two UIWindow objects in your application delegate, one with your regular view hierarchy, the other with the image, and animate the second to fade out (or however else you need to animate). Both windows should be visible, with the splash window being on top.
This approach is complicated, as you might have problems with rotation, depending on your regular view hierarchy. We've done this in our software, and it works well.
EDIT:
Adapted Solution (window approach, very simple):
UIImageView* splashView = [[UIImageView alloc] initWithImage:[UIImage imageWithBaseName:#"Default"]];
[splashView sizeToFit];
UIViewController* tmpVC = [UIViewController new];
[tmpVC.view setFrame:splashView.bounds];
[tmpVC.view addSubview:splashView];
// just by instantiating a UIWindow, it is automatically added to the app.
UIWindow *keyWin = [UIApplication sharedApplication].keyWindow;
UIWindow *hudWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0.0f, -20.0f, keyWin.frame.size.width, keyWin.frame.size.height)];
[hudWindow setBackgroundColor:[UIColor clearColor]];
[hudWindow setRootViewController:tmpVC];
[hudWindow setAlpha: 1.0];
[hudWindow setWindowLevel:UIWindowLevelStatusBar+1];
[hudWindow setHidden:NO];
_hudWin = hudWindow;
[UIView animateWithDuration:2.3f animations:^{
[_hudWin setAlpha:0.f];
} completion:^(BOOL finished) {
[_hudWin removeFromSuperview];
_hudWin = nil;
}];
Finally, credit goes to this guy.
A more simple approach would be to launch your application with the status bar hidden, have the view you would like to animate on the top of the view hierarchy, and after the animation is finished, display the status bar using [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]
You can add a subview to the App's UIWindow. Set the Y coordinate of the UIImageView's frame to -20 pixels in order to handle the status bar.
Add the Default PNG image to your window and tag it:
static const NSInteger kCSSplashScreenTag = 420; // pick any number!
UIImageView *splashImageView;
// Careful, this wont work for iPad!
if ( [[UIScreen mainScreen] bounds].size.height > 480.0f ) // not best practice, but works for detecting iPhone5.
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default-568h"]];
}
else
{
splashImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
}
splashImageView.frame = CGRectMake(0.0f, -20.0f, splashImageView.image.size.width, splashImageView.image.size.height);
splashImageView.tag = kCSSplashScreenTag;
[self.window addSubview:splashImageView];
[splashImageView release];
[self _fadeOutSplaceImageView];
Then fade it out
- (void)_fadeOutSplashImageView
{
UIView *splashview = [self.window viewWithTag:kCSSplashScreenTag];
if ( splashview != nil )
{
[UIView animateWithDuration:0.5
delay:0.0
options:0
animations:^{
splashview.alpha = 0.0f;
}
completion:^(BOOL finished) {
[splashview removeFromSuperview];
}];
}
}
I'm using a custom animation to present my view controllers. Here's the code:
-(void)launchCustomModal:(id)sender
{
UIButton *buttonClicked = (UIButton *)sender;
int selection;
selection = buttonClicked.tag;
[ticker removeFromSuperview];
ticker = nil;
if (selection == 3)
{
MyViewController *myVC = [[MyViewController alloc]initWithNibName:nil bundle:nil];
modalViewController= [[UINavigationController alloc]initWithRootViewController:myVC];
modalViewController.navigationBarHidden = YES;
[modalViewController setToolbarHidden:YES];
CGRect result = self.view.bounds;
result.origin.y = -result.size.height;
modalViewController.view.frame=result;
[self.view addSubview:modalViewController.view];
[UIView animateWithDuration:.375
animations:^{
modalViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}
completion:^(BOOL finished) {
NSLog(#"Finished");
}];
}
return;
}
I've noticed this method makes for a very laggy transition. If I launch the VC in a normal modal, it works quite smoothly. Also, if I animate just a view independent of a view controller, it also works perfectly smoothly. I'm wondering if there is something about the VC that might be causing it to animate so poorly? If its a symptom of something I'm doing or if view controllers are just not meant to be handled this way, etc. Any input is greatly appreciated. Thanks!
It was the CALayer shadows. removed them and it worked fine.
I'm try to popViewcontroller with transform scale animation
according this code when it begin transform it present the black screen instead of the Parent View
how to fix this ?
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
self.view.alpha = 1.0f;
self.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
} completion:^(BOOL finished) {
[[self navigationController] popViewControllerAnimated:NO];
}];
This is the expected behavior, because the view of the previous controller is not in the view hierarchy until popViewControllerAnimated: method is called and you call it after the animation finishes.
I don't think adding subviews directly to the view of the navigation controller is a good idea, but the following code should work for you.
UINavigationController* navigationController;
CGRect frame;
//keep a reference to the navigation controller as
//[self navigationController] won't work after pop is called
navigationController = [self navigationController];
//remember the frame of the view relative to navigation controller's view
frame = [navigationController.view convertRect:self.view.frame fromView:self.view.superview];
//pop this controller, this will add the view of the
//previous controller into the view hierarchy
[navigationController popViewControllerAnimated:NO];
self.view.frame = frame;
//add this view on top of the previous one
[navigationController.view addSubview:self.view];
[UIView animateWithDuration:0.5f
delay:0.0f
options:0
animations:^{
self.view.alpha = 0.0f;
self.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
}];
By the way, UIViewAnimationCurveEaseInOut is not a correct constant for the options parameter. You should use the constants that start with UIViewAnimationOption for this method.