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];
}
Related
Currently I am having a issue switching views using Core Animation. I want to fade through black switching to my next view.
Right now it does not do anything besides lose touch events from my original view.
What am I doing wrong in the code below?
Edit1 Code:
- (void)changeView1ToView2 {
CABasicAnimation *fadeout= [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeout setDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:)];
[fadeout setToValue:[NSNumber numberWithFloat:0]];
[fadeout setDuration:0.5];
[[self.view layer] addAnimation:fadeout forKey:#"alpha"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self.view addSubview:self.view2.view];
self.view2.view.frame = [UIScreen mainScreen].bounds;
[self.view2.view setAlpha:0];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
Ok I added self, look at my new code above. view2 is a UIViewController, thats why I am doing .view after it. The app is only going to be available on iOS 5 or up so thats not a problem. But what I am trying to achieve is switching views using Core Animation, and have each UIViewController manage their own views. I am just switching views using Core Animation instead of usual means.
If you're looking to have only one root view on screen at one time (and by the looks of that call to [UIScreen mainScreen].bounds, you are), then I'd suggest swapping the views at the UIWindow level. Without animations, this would look something like this:
// Assuming UIViewControllers called view1 and view2 as members of some
// (non-UIViewController) controller class (self, in this case)
//and that view1.view is in the application's window's subviews collection
[self.view1.view removeFromSuperview];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
In terms of not seeing the views actually swap, you need to ensure that your animation preserves the changes you make during the animation. Specifically, you need to set the following:
myAnimation.fillMode = kCAFillModeForwards;
myAnimation.removedOnCompletion = NO;
Your methods can then be adjusted to take all this into account. For example, your animationDidStop:finished: method might look like this:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.view1.view removeFromSuperview];
[self.view2.view setAlpha:0];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
fadein.fillMode = kCAFillModeForwards;
fadein.removedOnCompletion = NO;
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
You may need to muck around with it a bit to ensure that everything is firing correctly.
I wanted to create a modal view controller with a black background, but I want to make the alpha as 0.9, so I can still see the view behind it partially. How do I do this? I know I can use UIView and then just add that as a subview, however if I have a UINavigationController or UITabBarController, this won't cover the entire screen. Looking for some suggestions on this, as far as I know the solutions I've seen so far never dealt with a colored background.. most only wants a transparent background.
This will help you...
You just have to set background color as per your requirement.
Did you try
[viewController1 presentModalViewController:viewController2 animated:YES];
I haven't tried it myself but the documentation says it always presents it full screen. Then just set its view to have a backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.9];. (white = 0.0 is black). Add whatever subviews you want. Or set the background color to be full black and the alpha of the whole view to be 0.9. It depends if you want the subviews also to be a bit translucent.
I got this idea from https://gist.github.com/1279713
Prepare:
In the modal view xib (or scene using storyboard), I setup the full-screen background UIImageView (hook it with the .h file and give it a property "backgroundImageView") with 0.3 alpha. And I set the view (UIView) background color as plain black.
Idea:
Then in "viewDidLoad" of the modal view controller I capture the screenshot from the original status and set that image to the background UIImageView. Set the initial Y point to -480 and let it slide to Y point 0 using 0.4-second duration with EaseInOut animation option. When we dismiss the view controller, just do the reverse thing.
Code for the Modal View Controller Class
.h file:
#property (weak, nonatomic) IBOutlet UIImageView *backgroundImageView;
- (void) backgroundInitialize;
- (void) backgroundAnimateIn;
- (void) backgroundAnimateOut;
.m file:
- (void) backgroundInitialize{
UIGraphicsBeginImageContextWithOptions(((UIViewController *)delegate).view.window.frame.size, YES, 0.0);
[((UIViewController *)delegate).view.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
backgroundImageView.image=screenshot;
}
- (void) backgroundAnimateIn{
CGRect backgroundImageViewRect = backgroundImageView.frame;
CGRect backgroundImageViewRectTemp = backgroundImageViewRect;
backgroundImageViewRectTemp.origin.y=-480;
backgroundImageView.frame=backgroundImageViewRectTemp;
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
backgroundImageView.frame=backgroundImageViewRect;
} completion:^(BOOL finished) {
}];
}
- (void) backgroundAnimateOut{
CGRect backgroundImageViewRect = backgroundImageView.frame;
backgroundImageViewRect.origin.y-=480;
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
backgroundImageView.frame=backgroundImageViewRect;
} completion:^(BOOL finished) {
}];
}
In viewDidLoad, simply call:
[self backgroundInitialize];
[self backgroundAnimateIn];
In anywhere we dismiss the modal view controller, we call:
[self backgroundAnimateOut];
Please note that this will ALWAYS animate the background image. So if this modal view controller transition style (or the segue transition style) is not set to "Cover Vertical", you may not need to call the animation methods.
First add an UIImageView in you .h file
UIImageView *overLayImage;
now add below code in .m file on viewDidLoad()
overLayImage=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"overlay1px.png"]] ;
overLayImage.frame = self.view.frame;
overLayImage.frame = CGRectMake(0, 0, 1024, 748);
Where "overlay1px.png" is a transparent image of your color choice with height and width of 1px X 1px.
Now in your IBAction from where you want add your view with transparent backgroud add the below code
[self.view addSubview:overLayImage];
[vwTermsCondition setFrame:CGRectMake(262, 300, 500, 120)];
[vwTermsCondition.layer setCornerRadius:5.0f];
[vwTermsCondition.layer setShadowColor:[UIColor blackColor]];
[vwTermsCondition.layer setMasksToBounds:YES];
[self.view addSubview:vwTermsCondition];
In the above code vwTermsCondtion is the name of your view. This is for iPad appplication you can adjust height/width for iPhone.
Enjoy :)
I have a strange bug that I can't seem to figure out. I'm creating a little shining animation, which works perfectly, but for some reason stops when I navigate to another view via UINavigationController or UITabView (strangely modal view's don't affect it). Any ideas why, and how I can make sure the animation doesn't stop?
UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[whiteView setBackgroundColor:[UIColor whiteColor]];
[whiteView setUserInteractionEnabled:NO];
[self.view addSubview:whiteView];
CALayer *maskLayer = [CALayer layer];
maskLayer.backgroundColor = [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"ShineMask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-whiteView.frame.size.width,
0.0f,
whiteView.frame.size.width * 2,
whiteView.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.view.frame.size.width * 9];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 3.0f;
[maskLayer addAnimation:maskAnim forKey:#"shineAnim"];
whiteView.layer.mask = maskLayer;
Your maskAnim is retained by maskLayer, which is retained by whiteView's layer, which is retained by whiteView, which is retained by self.view. So this entire object graph will live until your view controller's view gets dealloc'd.
When you navigate away from your view controller, UIKit unloads your view to free up memory. When the view gets dealloc'd, so does your maskAnim. When you navigate back to your view controller, UIKit reconstructs your view hierarchy, either by reloading it from its .xib, or by calling loadView, depending on which technique you used.
So you need to make sure that the code you used to set up your maskAnim gets called again after UIKit reconstructs your view hierarchy. There are four methods you might consider: loadView, viewDidLoad, viewWillAppear:, and viewDidAppear:.
loadView is a sensible option if you use that method to build your view hierarchy (as opposed to loading it from a .xib), but you'll have to change your code so that it doesn't depend on the self.view property, which would trigger loadView recursively. viewDidLoad is also a good choice. With either of these options you you need to be careful because UIKit might resize your view after viewDidLoad if it wasn't constructed at the right size. This could cause bugs since your code depends on self.view.frame.size.width.
If you set up your animation in viewWillAppear: or viewDidAppear:, you can be sure that your view's frame will be the proper dimension, but you need to be careful with these methods because they can get called more than once after the view gets loaded, and you don't want to add your whiteView subview more than once.
What I'd probably do is make whiteView a retained property, and lazy load it in viewWillAppear: like this:
- (UIView *)setupWhiteViewAnimation {
// Execute your code above to setup the whiteView without adding it
return whiteView;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!self.whiteView) {
self.whiteView = [self setupWhiteViewAnimation];
[self.view addSubview:self.whiteView];
}
}
- (void)viewDidUnload {
self.whiteView = nil; // Releases your whiteView when the view is unloaded
[super viewDidUnload];
}
- (void)dealloc {
self.whiteView = nil; // Releases your whiteView when the controller is dealloc'd
[super dealloc];
}
So I've got a problem with buttons and animations. Basically, I'm animating a view using the UIView animations while also trying to listen for taps on the button inside the view. The view is just as large as the button, and the view is actually a subclass of UIImageView with an image below the button. The view is a subview of a container view placed in Interface Builder with user interaction enabled and clipping enabled. All the animation and button handling is done in this UIImageView subclass, while the startFloating message is sent from a separate class as needed.
If I do no animation, the buttonTapped: message gets sent correctly, but during the animation it does not get sent. I've also tried implementing the touchesEnded method, and the same behavior occurs.
UIImageView subclass init (I have the button filled with a color so I can see the frame gets set properly, which it does):
- (id)initWithImage:(UIImage *)image {
self = [super initWithImage:image];
if (self != nil) {
// ...stuffs
UIButton *tapBtn = [UIButton buttonWithType:UIButtonTypeCustom];
tapBtn.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[tapBtn addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
tapBtn.backgroundColor = [UIColor cyanColor];
[self addSubview:tapBtn];
self.userInteractionEnabled = YES;
}
return self;
}
Animation method that starts the animation (if I don't call this the button works correctly):
- (void)startFloating {
[UIView beginAnimations:#"floating" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:10.0f];
self.frame = CGRectMake(self.frame.origin.x, -self.frame.size.height, self.frame.size.width, self.frame.size.height);
[UIView commitAnimations];
}
So, to be clear:
Using the UIView animation effectively disables the button.
Disabling the animation causes the button to work.
The button is correctly sized and positioned on screen, and moves along with the view correctly.
This resolves the issue:
[UIView animateWithDuration:20 delay: 0.0 options: UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
...
The animation is just eye candy. The animation lags behind the actual movement of the view. The button is already at the destination point when the animation starts. You just see a movie of the view/button moving.
If you want a button to be clickable during the animation, you'll have to make the animation yourself.
... was experiencing this same problem because my code was doing one large animation per block. I made an NSTimer based solution, like the one suggested above, and it worked... yet the movement was jerky (unless I inserted animation within every timer event trigger).
So, since animation was required anyway, I found a solution which requires no timer. It animates only a short distance and thus the button click is still accurate, with only a small error which is my case is very unnoticeable in the UI, and can be reduced depending on your params.
Note below that the error at any given time is < 15.0, which can be reduced for more accuracy depending on your animation speed requirements. You can also reduce the duration time for more speed.
- (void)conveyComplete:(UIView*)v
{
[self convey:v delay:0];
}
- (void)convey:(UIView*)v delay:(int)nDelay
{
[UIView animateWithDuration:.5
delay:nDelay
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations: ^
{
CGRect rPos = v.frame;
rPos.origin.x -= 15.0;
v.frame = rPos;
}
completion: ^(BOOL finished)
{
[self conveyComplete:v];
}];
}
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.