I have a data entry application that has the user enter about 6 pieces of information all on different views in a navigation controller. This works fine, but once the user gets used to the application the time it takes for the next screen to appear slows the user down.
I tried the application without the animations, but it doesn't feel quite right. Is there a way to get the animations to occur quicker? I'm primarily using a navigation controller, table views, and picker views.
There's going to be a penalty each time you load a new view, you could attempt to consolidate screens using a scroll view or a different layout.
Also, if you are loading any unnecessary graphics you may want to remove them.
You could also add each view as a subview yourself in which case you have control over the animation duration among other things. This code will do that for you, although beware as I just wrote it and did not test it (The transition style and boolean parameters can be removed as they do nothing right now).
UIViewControllerExtendedPresentModalViewController.h
#import <Foundation/Foundation.h>
typedef enum _ExtendedModalTransitionStyle
{
ExtendedModalTransitionStyleTopDown
} ExtendedModalTransitionStyle;
#interface UIViewController ( ExtendedPresentModalViewController )
- (void)presentModalViewController: (UIViewController*)modalViewController
withTransitionStyle: (ExtendedModalTransitionStyle)style
animated: (BOOL)animated;
- (void)dismissModalViewController: (UIViewController*)modalViewController
withTransitionStyle: (ExtendedModalTransitionStyle)style
animated: (BOOL)animated;
#end
UIViewControllerExtendedPresentModalViewController.m
#import "UIViewControllerExtendedPresentModalViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation UIViewController ( ExtendedPresentModalViewController )
- (void)presentModalViewController: (UIViewController*)modalViewController
withTransitionStyle: (ExtendedModalTransitionStyle)style
animated: (BOOL)animated
{
[modalViewController retain]; // we'll need this for a little while, hang on to it.
CATransition* transition = [CATransition animation];
[transition setDuration: 0.4];
[transition setTimingFunction:
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]];
[transition setType: kCATransitionMoveIn];
[transition setSubtype: kCATransitionFromBottom];
[[[self view] layer] addAnimation: transition
forKey: nil];
[[self view] addSubview: [modalViewController view]];
}
- (void)dismissModalViewController: (UIViewController*)modalViewController
withTransitionStyle: (ExtendedModalTransitionStyle)style
animated: (BOOL)animated
{
CATransition* transition = [CATransition animation];
[transition setDuration: 0.4];
[transition setTimingFunction:
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]];//kCAMediaTimingFunctionEaseInEaseOut]];
[transition setType: kCATransitionReveal];
[transition setSubtype: kCATransitionFromTop];
[[[[modalViewController view] superview] layer] addAnimation: transition
forKey: nil];
[[modalViewController view] removeFromSuperview];
[modalViewController release]; // all done, we can let this go.
}
#end
Are you reusing cells in the table view? Make sure to really reuse them, in other words do all the setup of the cell inside the if(cell==nil) case and only apply the data in the common case (applying to both reuse and newly created).
Be aware of the performance hit that transparency can have in a cell. Often it seems that you have to make things transparent but maybe you don't because UITableViewCell is aware of the problems. Turning off transparency in some objects might seem wrong but is worth a try to see if it actually works. Most of the cost of scrolling a cell is the compositing as the cell moves, not creation of the cell initially.
Another thing that can help is doing the compositing of the view that you apply to your cell in advance rather than adding all the views to the cell, then you apply just one premade view to your cell.
If you are actually animating views in the scrolling cells you may need to rethink that or at least use some simplification to make it a little less taxing on the device.
You might also consider adopting Matt Gallagher's strategy for cell handling - it stops your cellForRowAtIndexPath turning into one long, nasty set of ifs.
Related
I am using the following code to launch a view controller named secondViewController.
[self.navigationController pushViewController:secondViewController animated:YES];
The code successfully causes the secondVC to slide in from the right to left
After the user is done I would like the user to be able to go back to the first View Controller. For this purpose I have wired a back button to a method that uses similar code, however in this case I would like it to slide from left to right, i.e. the opposite direction. However, not surprisingly the return VC also goes from right to left.
[self.navigationController pushViewController:returnViewController animated:YES];
Is there a way to reverse the direction so that in the second case, it slides from left to right?
EDIT:
Using the code from the answer suggested by Brendan, I have added the following method to override the pushViewController. I am calling it the same way but have had to comment the swapButtonsForViewController as the selector SwapButtons is not recognized. Not sure what it really does.
In addition, if you call it as super, I get an error: no visible#interface in the super VC for this VC. Not sure how to fix that. If I change super to self, error disappears but it still goes right to left.
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
UIView *theWindow = self.view ;
if( animated ) {
CATransition *animation = [CATransition animation];
[animation setDuration:0.45f];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[theWindow layer] addAnimation:animation forKey:#""];
}
//make sure we pass the super "animated:NO" or we will get both our
//animation and the super's animation
[super pushViewController:viewController animated:NO];
// [self swapButtonsForViewController:viewController];
}
Oops, a few things in addition to my comment:
Did you inherit from UINavigationController? That will help, if you didn't do that already.
Also, the link I shared with you will replace the direction for both cases, unfortunately. I should have read the question more carefully, apologies.
Good news though! Try both of these:
https://stackoverflow.com/a/16535931/5806860
https://stackoverflow.com/a/16384808/5806860
Those are similar solutions, but have minor changes that could potentially solve your problem.
I'm currently performing a curl up animation by doing the following:
CATransition *animation = [CATransition animation];
animation.type = #"pageCurl";
animation.subtype = kCATransitionFromTop;
animation.fillMode = kCAFillModeForwards;
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[[self.view.window layer] addAnimation:animation forKey:nil];
This animation will simply perform a page curl animation but in the end you are left looking at the same view that you started out with. No REAL transition ever occurred.
Now using animation blocks I know you can do something to the effect of:
[UIView transitionFromView:self.view
toView:aNewView // a view to transition to
duration:1
options:UIViewAnimationTransitionCurlUp|UIViewAnimationOptionCurveEaseIn
completion:NULL];
However, using animation blocks you are transitioning to a new view, aNewView, which is different from the CATransition animation above which reuses the same view (no real transition ever occurs). The problem with the animation block is that you have to actually create the new view to transition to which is cumbersome in my case because the view is rather complicated and I'd rather not create a UIView subclass.
Is there a way to perform the above CATransition animation using animation blocks while getting around the difficulties of having to rebuild a view or create a custom subclass?
Ok - so I figured it out for anyone who is interested. It's actually super simple. You can simply use the method: transitionWithView:duration:options:animations:completion:
For example:
[UIView transitionWithView:self.view
duration:1
options:UIViewAnimationOptionTransitionCurlUp|UIViewAnimationCurveEaseIn
animations:^{ // do anything you want here }
completion:NULL];
That's it. This will show a transition animation back to the same view. You can make any changes you want to the new view (maybe display some new text, whatever...) in the animation block.
I hope this helps someone out down the line...
I have a UITableViewCell subclass that does its drawing in a drawRect: method. The whole rectangle is custom drawn, including the background.
I was able to get very complex cells while keeping the scrolling very smooth.
My problem: I call [table deselectRowAtIndexPath:path animated:YES]; whenever the view appears, in order to comply with Apple's HIG. However, no animation occurs. It worked when I had a custom view (created with subviews) that was transparent (so Apple's background would appear below), of course. Now it doesn't.
My drawRect: is called once during the "animation time", about halfway through. I think this happens because it's animating the highlighted property of the cell from 1 to 0 and it snaps to 0 when it drops below 0.5.
How can I animate this transition? My guess would be to use the usual beginAnimations: etc. and animate a custom floating point field in my cell object from 1 to 0. Will this call drawRect: repeatedly to animate?
Update
I managed to get this almost working. I've overridden setSelected:animated: like so:
- (void) setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:NO];
if (animated) {
[CATransaction begin];
CATransition* animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.duration = 0.6;
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[view.layer addAnimation:animation forKey:#"deselectRow"];
[CATransaction commit];
}
}
This works perfectly if the table view is on-screen. But if I'm returning to the table view from navigation (back), instead of fading from selected to not selected, it fades from invisible to not selected. What can cause this?
A little late answer, but might still be useful to you and others. What you need to do, is to assign your custom selected view before messaging the super, like so:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
UIView *view = [[UIView alloc] initWithFrame:self.frame];
view.backgroundColor = [UIColor colorWithRed:.9 green:.0 blue:.125 alpha:1.0];
self.selectedBackgroundView = view;
[super setSelected:selected animated:animated];
}
I want to flip my new ModalView with very high performance, but the new View has a lot of subviews so the performance of the Flip-Effect is very bad. Actually i do it with:
[controller setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentModalViewController:backSideController animated:YES];
I also tried it with
CATransition *transition = [CATransition animation];
transition.duration = 0.75;
[transition setType: #"flip"];
[transition setSubtype:#"fromRight"];
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[transition setFillMode:#"extended"];
[[self.view layer] addAnimation:transition forKey:nil];
[self.view addSubview: backSideController.view];
[CATransaction commit];
With Core-Animation it works a little bit faster ... ware there further ways to opimize this task? e.g. Adding view when animation stops and just flipping a screenshot until animation stops?
Try accessing backsideController.view before you begin the animations. This will cause backsideController's loadView and viewDidLoad to be called. I'm guessing that this is your performance hit -- that all that loading & allocating is causing the animation to stutter.
You don't need anything fancy, you can do something like:
if (backsideController.view == nil)
NSLog(#"Where's my view?!");
before your other code, above.
I don't believe that having many-many subviews causes performance problems on the flip; I'm pretty sure (without looking at your code or checking in instruments, which you should do!) that the problem is the time it takes to load and allocate the view components.
Also, I'd stick with presentModalViewController, if it does what you want. Having all that extra code in your 2nd example -- unless it's needed for functionality -- is just maintenance headache.
I am facing problems in flipping views in iPhone.
I have two views in appDelegate. I want to flip them once user clicks on a button.
I have the following code:
CATransition *transition = [CATransition animation];
transition.duration = 0.75;
[transition #"twist"];
[transition setSubtype:#"fromRight"];
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[transition setFillMode:#"extended"];
[[window layer] addAnimation:transition forKey:nil];
[window addSubview:self.s.view];
[CATransaction commit];
But this is not working. Do anybody knows a better way to flip the views on window side.
What I am doing is calling the method from appDelegate in the respective viewControllers to flip the views.
If you're using the 3.0 SDK and all you want is a simple flip transition (ala the Weather app) then you don't need to go down to CATransition. The higher-level UIView animation transitions will do what you want but with 3.0 there is an even easier way: simply present your new view as a modal view controller and set the modal transition style to flip. From within the first controller:
UIViewController *controllerForSecondView = ..;
controllerForSecondView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controllerForSecondView animated:YES];
Flip back again by using dismissModalViewController.
Documentation Reference
#Luke - thanks, this sample helped me...1 correction though (based on UIViewController.h)
UIViewController *controllerForSecondView = ..;
controllerForSecondView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controllerForSecondView animated:YES];
From the header file comments:
// Defines the transition style that will be used for this view controller when it is presented modally. Set this property on the view controller to be presented, not the presenter.
// Defaults to UIModalTransitionStyleSlideVertical.
#property(nonatomic,assign) UIModalTransitionStyle modalTransitionStyle
See The Elements sample code. Particularly AtomicElementViewController -flipCurrentView.