iOS Animation within DrawRect() - iphone

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.

Related

Animate a UIImageView from center of the UIImageView

I have a UIImageView that should animate from size (0,0) --> (93,75)
I have the following
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionTransitionNone
animations:^{
imgButton.layer.anchorPoint = CGPointMake(imgButton.bounds.size.width/2, imgButton.bounds.size.height/2);
CGRect btnFrame = imgButton.frame;
btnFrame.size.width = 105;
btnFrame.size.height = 85;
imgButton.frame = btnFrame;
}
completion:^(BOOL finished) {
[self animageButton2:imgButton];
}];
This is working but the only problem I have is that it is not animating from the center of the UIImageView but from the top left corner.
Anybody got a clue?
I've solved it with the help of Borrden. This is what my solution was.
First create an imageview
UIImageView *imgLineup = [[UIImageView alloc]initWithFrame:CGRectMake(10, y, 93, 75)];
imgLineup.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.0, 0.0);
And then to the following.
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^{
imgButton.layer.anchorPoint = CGPointMake(0.5, 0.5);
imgButton.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);
}
completion:^(BOOL finished) {
NSLog(#"done");
}];
just try with this. import #import <QuartzCore/QuartzCore.h> then put below code while adding the uimageView into subView of self.view
CABasicAnimation *zoomAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
[zoomAnimation setDuration:0.8];
[zoomAnimation setRepeatCount:1];
[zoomAnimation setFromValue:[NSNumber numberWithFloat:0.1]];
[zoomAnimation setToValue:[NSNumber numberWithFloat:1.0]];
[zoomAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[imageView layer] addAnimation:zoomAnimation forKey:#"zoom"];
What i suggest may be kind of hack but it will be an easier way to achieve what you want:
You set the initial frame of imageView as:
CGRectMake(center, center, 0, 0);
So in your case it will be something closer to :
CGRectMake(46, 37, 0, 0);
Then animate it with new rect of something like this:
CGRectMake(0, 0,93,75);
You can use simple UIViewAnimation for this;
[UIView beginAnimations:#"zoom" context:NULL];
[UIView setAnimationDuration:0.3];
imageView.frame = CGRectMake(0, 0, 93, 75); ;
[UIView commitAnimations];
Hope It works
What you are trying to achieve is a scale transform i.e. animating UIView size from size(0,0) to size(w,h). Same effect can be achieved by CGAffineTransformMakeScale.
Initially when you create the view apply CGAffineTransformMakeScale(0.0,0.0) then animate size scale up to CGAffineTransformMakeScale(1.0,1.0).
Code:
// Create the view and apply correct frame
UIView *tagView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 100, 100)];
tagView.tag = kTag;
//Apply scale transform, to make size(0,0)
tagView.transform = CGAffineTransformMakeScale(0,0);
[self.view addSubview:tagView];
/// ............
UIView *tagView = [self.view viewWithTag:kTag];
//Now animate the view size scale up
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
tagView.transform = CGAffineTransformMakeScale(1.0,1.0);
}
completion:NULL
];
This animation will happen about the center point of the view as you want, thus avoiding the need to play with anchorPoint and frame, which can be tricky. Read more about the relation between frame and anchorPoint from this answer.

UIView Animations of custom view lagging

I created a UIView Subclass and are using drawing rounded rects with UIBezierpath and fill it with some gradient.
In short this is what the subclass draw rect looks like:
CGRect frame1 = //size of Frame 1
CGRect frame2 = //size of Frame 2
//gradientingStart
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (options == false) {
self.color = [self createRandomColor];
}
NSArray *gradientColors = [NSArray arrayWithObjects:
(id)[UIColor colorWithHue:color saturation:saturationVal brightness:brightnessVal alpha:1].CGColor,
(id)[UIColor colorWithHue:color+0.04 saturation:saturationVal+0.15 brightness:brightnessVal-0.15 alpha:1].CGColor, nil];
CGFloat gradientLocations2[] = {0.25,1};
CGGradientRef gradient2 = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, gradientLocations2);
CGContextSaveGState(context);
//gradientingEnd
UIBezierPath *result =
[UIBezierPath bezierPathWithRoundedRect: frame1 cornerRadius:radius];
[result appendPath:
[UIBezierPath bezierPathWithRoundedRect: frame2 cornerRadius:radius]];
[result setLineWidth:3];
[[UIColor blackColor]setStroke];
[result stroke];
[result fill];
[result addClip];
CGContextDrawLinearGradient(context, gradient2, CGPointMake(0, 0), CGPointMake(0, self.bounds.size.height), 0);
//important to prevent leaks
CGGradientRelease(gradient2);
CGColorSpaceRelease(colorSpace);
//-------
UIImage *texture = [UIImage imageNamed:#"texture2.png"];
[texture drawInRect:CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.bounds.size.height)];
Now I'm creating several instances of this in my view controller and trying the move them with animations like this:
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
costumview.alpha = 0;
[costumview setFrame:CGRectMake(320,0,320,420)];
[self reorderViewsFrom:costumview.intValue];
overlay.alpha = 0;
}completion:^(BOOL finished){
}];
But unfortunately the animation is on a device very laggy and the more (5-10) costum views are display the worse it gets.
How can i improve the performance and provide smooth animations? I think changing the costum view is not an alternative because this would destroy the sense of the application. Are there any faster ways to animate the costum view?
Your drawRect method is very intense. Drawing those gradients and rounded edges are performance heavy routines, and because your changing the frame of your image in the animation, your drawRect is getting called very often.
I would add an NSLog inside your draw rect and see how many times it gets called when you animate your custom view. You might be surprised.
You could try using:
view.layer.shouldRasterize = YES
This way you render a bitmap of the custom view before you animate it. After animating the frame change again, you should invoke drawRect again and disable shouldRasterize.
Read more about shouldRasterize here:
https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instp/CALayer/shouldRasterize
I pointed out that using some CALayer transformation (rounding corners) causing the lag - unfournately this wasn't in the snippet I posted.
I avoided it by creating the rounded corners with a own uiview subclass. This makes the app a lot smoother. All these rasterization stuff didn't do it for me...

Infinite animation not being interrupted by home button

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.

CAScrollLayer doesn't scroll!

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];

addSubview animation

I have main UIView where I display different data. Then I put a button, which displays subview like this:
- (IBAction) buttonClicked:(id)sender
{
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
UILabel *newLabel = [[UILabel alloc] initWithFrame:CGRectMake(25,25,50,25)];
newLabel.text = #"some text";
[newView addSubview:newLabel];
[self.view addSubview:newView];
[newLabel release];
[newView release];
}
newView appears fine, but it doesn't animate itself any way, it just appears immediately. How can I add some kind of animation when newView appears? Like zoom or slide down or something like that. Is there some simple code?
[UIView transitionWithView:containerView duration:0.5
options:UIViewAnimationOptionTransitionCurlUp //change to whatever animation you like
animations:^ { [containerView addSubview:subview]; }
completion:nil];
Hi You could use the UIView Animations:
[newView setFrame:CGRectMake( 0.0f, 480.0f, 320.0f, 480.0f)]; //notice this is OFF screen!
[UIView beginAnimations:#"animateTableView" context:nil];
[UIView setAnimationDuration:0.4];
[newView setFrame:CGRectMake( 0.0f, 0.0f, 320.0f, 480.0f)]; //notice this is ON screen!
[UIView commitAnimations];
The SDK will now figure this out for you and animate the view to the positions you gave it.
This works for most properties of UIViews: alpha, scale, bounds, frames, etc.
There are also build in animations as flip and curl:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
forView:newView
cache:YES];
[self.navigationController.view addSubview:settingsView.view];
[UIView commitAnimations];
Hope this helps out getting you started:)
Let's do this in swift.
var redView = UIView(frame:CGRectMake(20,20,100,100))
redView.backgroundColor = UIColor.redColor
redView.alpha = 0.0
view.addSubview(redView)
UIView.animateWithDuration(0.25) { () -> Void in
redView.alpha = 1.0
}
Adding a subview cannot be animated by simply putting it in an animateWithDuration block. And it can't be animated using hidden.
link against QuarzCore framework
#import <QuartzCore/QuartzCore.h>
CATransition *transition = [CATransition animation];
transition.duration = 1.0;
transition.type = kCATransitionPush; //choose your animation
[newView.layer addAnimation:transition forKey:nil];
[self.view addSubview:newView];
I found that even with the animation blocks and options, trying to animate addSubview alone would not do anything for me. I found a workaround which is to set the subview's alpha to 0.0, add the subview, and then animate the setting of the alpha from 0.0 to 1.0. This gives me the fade in effect I was looking for.
Hope this helps someone else.
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
redView.backgroundColor = [UIColor redColor];
redView.alpha = 0.0;
[self.view addSubview:redView];
[UIView animateWithDuration:0.5 animations:^{
redView.alpha = 1.0;
}];
Swift 5
UIView.transition(with: renderView, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.renderView.addSubview(emojiView)
self.renderView.bringSubviewToFront(emojiView)
}, completion: nil)
in code example renderView is view to what you will add your subView