I have several objects of a subclass of uiview, inside the main view of a viewcontroller that I animate infinitely by calling the following method in the class:
- (void)hover:(NSNumber *)upDown {
int sense = [upDown intValue];
[UIView animateWithDuration:0.8 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
CGRect frame = self.frame;
frame.origin.y += (sense==1?1:-1) * 5;
self.frame = frame;
}
completion:^(BOOL finished){
[self hover:[NSNumber numberWithInt:(sense==1?0:1)]];
}];
}
It works great except that in the device, when the home button is pressed, the app semi-freezes (the app eventually goes to background by pressing the home button repeatedly for quite a while) and any interaction with other buttons stops working. It works fine in any other case, i.e. as long as I don't press the home button I can navigate through controllers, press buttons, etc... and on the simulator.
Any ideas?
UPDATE: The culprit seems to be the shadow I'm applying to the views I'm animating
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 1.0;
self.layer.shadowRadius = 5.0;
This seems to be causing some kind of overhead that only affects the app not being able to go into background state???
Anyone encountered this?
UPDATE: In the end I decided to get rid of that code and draw the shadow with Quartz in the drawRect: method. I suspect the problem might have to do with the snapshot that the iPhone takes before going into background mode and the shadow applied to the layer outside the bounds, but it's just a guess.
Basically the problem has to do with this way of obtaining a rounded shape and shadow:
[self.layer setCornerRadius:radius];
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.2;
self.layer.shadowRadius = 5;
self.layer.shadowOffset = CGSizeMake(0, 10);
The shadow draws outside the frame of the view and the iPhone's process of going into background state doesn't seem to like that when animating.
As I mention in the update, I got rid of this and used Quartz to draw both shape an shadow inside the frame. There are many posts on how to do this but here it goes anyway:
-(void)drawRect:(CGRect)rect
{
const CGFloat *cComps = CGColorGetComponents([UIColor blackColor]).CGColor;
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat center = rect.size.width / 2.0;
CGFloat radius = center;
CGContextBeginPath(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, 10), 4, [UIColor colorWithWhite:0 alpha:0.1].CGColor);
CGContextAddArc(context, center, center, radius, 0, 2*M_PI, 1);
CGContextSetRGBFillColor(context, cComps[0], cComps[1], cComps[2], 1.0);
CGContextFillPath(context);
}
NOTE: I set the height of the view to +15px to accommodate the shadow at the bottom.
Hope it helps somebody. It certainly drove me mad for a couple of days...
I recently ran into this problem and this post was incredibly helpful in identifying the culprit. Turns out though, that simply setting shouldRasterize on the shadowed layers is sufficient to prevent the freeze. (You should also set rasterizationScale to be the same as the device's main screen's scale.)
All of my shadowed layers were relatively static and weren't being constantly animated, so YMMV.
Your standard shadow adding code:
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowOffset = CGSizeMake(0, 1);
layer.shadowOpacity = 0.5;
layer.shadowRadius = 1.0;
This is what you need to add:
layer.rasterizationScale = [[UIScreen mainScreen] scale];
layer.shouldRasterize = YES;
My first thought is that you should implement a BOOL in your .h file (let's call it stopGoing). Then do the following:
- (void)hover:(NSNumber *)upDown {
if(!stopGoing){
int sense = [upDown intValue];
[UIView animateWithDuration:0.8 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
CGRect frame = self.frame;
frame.origin.y += (sense==1?1:-1) * 5;
self.frame = frame;
}
completion:^(BOOL finished){
[self hover:[NSNumber numberWithInt:(sense==1?0:1)]];
}];
} else {
// do something else
}
}
Then, in your IBAction that you have linked to the button, just do this:
-(IBAction)myButton:(id)sender{
stopGoing = YES;
}
That should effectively stop the loop, without being too invasive.
Related
In my iphone application I have several images. lets say some small square images. what I want is I want that small objects appear from the top of the screen at rondom times and leaves the screen from the bottom. I mean, for example, object randomly appeared from the x:30 y:0 point (x value must be random) and leaves from the x:30 y:460 with an animation.
I am programming an 2d racing game and they are the cars. hope it is clear.
edit: well I tried to change the frame the numbers belong to something else. But I could not have the frame of the image and also I could not figure out how to generate position randomly more than one time. since I was planning to put it on the viewDidLoad.
[UIView beginAnimations:#"myAnimation" context:nil];
CGRect Frame = image.frame;
if(Frame.origin.y == 340){
Frame.origin.y = 260;
}else{
Frame.origin.y = 340;
}
image.frame = Frame;
[UIView commitAnimations];
Your example code doesn't show what you are asking for. It's also really hard to guess what you want.
Here's my literal interpretation of what you have asked for
for (int i = 0; i < 100; i++) {
CGRect startFrame = CGRectMake(arc4random_uniform(320), -50, 50, 50);
CGRect endFrame = CGRectMake(arc4random_uniform(320),
CGRectGetHeight(self.view.bounds) + 50,
50,
50);
UIView *animatedView = [[UIView alloc] initWithFrame:startFrame];
animatedView.backgroundColor = [UIColor redColor];
[self.view addSubview:animatedView];
[UIView animateWithDuration:2.f
delay:i * 0.5f
options:UIViewAnimationCurveLinear
animations:^{
animatedView.frame = endFrame;
} completion:^(BOOL finished) {
[animatedView removeFromSuperview];
}];
}
If you stick this in viewDidAppear: then you'll see 100 objects animate in the way you want. Obviously this is extremely inefficient and you may want to look at recycling the views etc but this shows how to animate views with UIView animation with some random points
I'm trying to slide down an image in an UIImageView, but I don't know which combination of UIContentMode and animation property is the right one to make that happen.
The image should always have the same size and should not be streched... all I want is, that nothing is visible first and then the frame extends and reveals the image.
Maybe it's easier if you see what I mean:
So, it sounds rather easy, but what UIContentMode should I use for the UIImageView and what property should I animate? Thank you!
I took your lead and made a screencast as well. Was this what you had in mind?
I put the animation repeating indefinitely so it would be easier to capture with a video, but it can be started at the press of a button, as well as frozen in place, showing the popover and its contents, until reversed to be hidden again.
I used Core Animation for that, instead of animating a UIView, since I wanted to use the mask property of CALayer to hide the popover and reveal it with a sliding animation.
Here is the code I used (same as in the video):
- (void)viewDidLoad
{
// Declaring the popover layer
CALayer *popover = [CALayer layer];
CGFloat popoverHeight = 64.0f;
CGFloat popoverWidth = 200.0f;
popover.frame = CGRectMake(50.0f, 100.0f, popoverWidth, popoverHeight);
popover.contents = (id) [UIImage imageNamed:#"popover.png"].CGImage;
// Declaring the mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0, - popoverHeight, popoverWidth, popoverHeight);
maskLayer.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f].CGColor;
// Setting the animation (animates the mask layer)
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
animation.byValue = [NSNumber numberWithFloat:popoverHeight];
animation.repeatCount = HUGE_VALF;
animation.duration = 2.0f;
[maskLayer addAnimation:animation forKey:#"position.y"];
//Assigning the animated maskLayer to the mask property of your popover
popover.mask = maskLayer;
[self.view.layer addSublayer:popover];
[super viewDidLoad];
}
NOTE: You have to import the QuartzCore framework into your project and write this line in your header file: #import <QuartzCore/QuartzCore.h>.
Tells if this works for you or if you need any more help setting this up.
Try this code.
Consider the UIImageView as imageView.
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect imageRect = imageView.frame;
CGRect origImgRect = imageRect;
imageRect.size.height = 5;
imageView.frame = imageRect;
[UIView animateWithDuration:2.0
animations:^{imageView.rect = origImgRect;}
completion:^(BOOL finished){ }];
I'm trying to get CALayer working so I can start practicing some animations tricks that I would like to learn. At the moment I want to scale and move things along the z-axis.
Right now I see nothing when I run this code. I would expect to see a black square. But instead nothing is appearing. The code compiles (obviously) and there are no warnings. Is there something I am missing?
I've be trying to learn by reading this tutorial
http://watchingapple.com/2008/04/core-animation-3d-perspective/
But obviously there is a hole in my knowledge somewhere thats left me stumped.
-() addAnImageInTheBackground
{
CALayer *theImage = [CALayer layer];
theImage.backgroundColor = [UIColor blackColor];
theImage.anchorPoint = CGPointZero;
CGRect frame;
frame.origin.x = 10;
frame.origin.y = 10;
frame.size.height = 20;
frame.size.width = 20;
theImage.frame = frame;
theImage.bounds = frame;
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -2000;
theImage.sublayerTransform = transform;
NSNumber *value = [NSNumber numberWithInt:-10];
[theImage setValue:value forKeyPath:#"transform.translation.z"];
[[[self view] layer] addSublayer:theImage];
}
The line theImage.bounds = frame is wrong. You don't want to change the bounds. It's also possible your transform is wrong, but I can't recall enough about transformation matrices to remember what .m34 does ;)
I'm trying to animate CGContext drawings within the drawRect method of a view.
Code Snippet
//Initial Attempt
[UIView animateWithDuration:10
animations:^{
CGRect marker = CGRectMake(leftLineX-5.0, yCoord-5.0, 10.0, 10.0);
CGContextSetStrokeColorWithColor(c, _barColor.CGColor);
CGContextSetFillColorWithColor(c, _labelColor.CGColor);
CGContextFillEllipseInRect(c, marker);
CGContextStrokeEllipseInRect(c, marker);
}];
//Next approach
CGRect marker = CGRectMake(255.0, 255.0, 10.0, 10.0);
UIView * markerView = [[UIView alloc] initWithFrame:marker];
[self addSubview:markerView];
CGContextAddEllipseInRect(c, marker);
CGContextSetStrokeColorWithColor(c, [UIColor redColor].CGColor);
CGContextSetFillColorWithColor(c, [UIColor blueColor].CGColor);
CGContextFillEllipseInRect(c, marker);
CGContextStrokeEllipseInRect(c, marker);
[UIView animateWithDuration:3 animations:^{
markerView.alpha = 0.0;
[markerView setNeedsDisplay];
}];
What I know
I think that because the context is being drawn in the current drawRect view that perhaps the UIView isn't aware of the changes made. What's the best way of approaching this situation? I would like to avoid subclassing if possible.
If anyone was wondering, the correct approach was to subclass here.
Maybe it's because it's late. Whatever the reason I can't figure out why I'm having trouble with a simple CSScrollLayer example I'm trying. I add a 50 pixel Eclipse icon to a view based project and in my initialize method (called from initWithNibName:bundle:) I have this:
-(void) initialize
{
CAScrollLayer *scrollLayer = [CAScrollLayer layer];
scrollLayer.backgroundColor = [[UIColor blackColor] CGColor];
CGRect bounds = self.view.bounds;
scrollLayer.bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
scrollLayer.contentsRect = CGRectMake(0, 0, bounds.size.width + 800, bounds.size.height + 800);
scrollLayer.borderWidth = 2.5;
scrollLayer.borderColor = [[UIColor redColor] CGColor];
scrollLayer.position = CGPointMake(self.view.center.x, self.view.center.y - 20);
scrollLayer.scrollMode = kCAScrollBoth;
[self.view.layer addSublayer:scrollLayer];
UIImage *image = [UIImage imageNamed:#"eclipse32.gif"];
for(int i=0; i<6; i++) {
layer = [CALayer layer];
layer.backgroundColor = [[UIColor blackColor] CGColor];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.contents = (id)[image CGImage];
layer.position = CGPointMake(layer.bounds.size.width * i, self.view.center.y);
[scrollLayer addSublayer:layer];
}
// [button removeFromSuperview];
// [self.view addSubview:button];
// self.view.userInteractionEnabled = YES;
[image release];
}
The scroll layer shows, the icon is repeated on the layer I have a border around the edge of the screen. Everything is lovely except I can't scroll the icons. I've tried with/without setting scroll mode. I've tried with a single stretched icon that falls off screen. I've tried everything. What am I doing wrong?
CAScrollLayer does not plug into the UIEvent chain so you need to manually add code to modify the scroll offsets on touch events. It is also very tricky to get the scrolling momentum like UIScrollView does. I would strongly recommend re-implementing with UIScrollView -- it will be remarkably simpler. The only other option is to do manual touch event handling, which is painful! I tried to get a nice scroller with CAScrollLayer and gave up after implementing a few awkward scrolling behaviors manually.
The general rule I follow with using CALayer or UIView's is to use CALayer objects when I don't need any user interaction. If the user is going to touch it, use the UIView, it's a very lightweight object so the overhead is negligible.
That said, I've found CAScrollLayer remarkably useless! Why not just use a CALayer and modify the bounds? There's probably some use to it, but it is no replacement for UIScrollView.
Try adding this to your example to scroll the layer programmatically:
- (void)scroll {
[scrollLayer scrollToPoint:scrollPoint];
scrollPoint.x += 10;
[self performSelector:_cmd withObject:nil afterDelay:0.1];
}
and add this at the end of the initialize method:
scrollPoint = CGPointMake(0, 0);
[self scroll];