UIView Animations of custom view lagging - iphone

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...

Related

CAShapeLayer sublayer of a UIView in a UIScrollView is extremely slow

I have a custom UIView which is supposed to be a round rectangle which, when you begin editing a UILabel inside, the round rectangle will grow its shape (CABasic animation on a CAShapeLayer mask path and outline path). So to be clear, the custom UIView is organized like this:
MyCustomView has a ClipView.
ClipView uses drawRect to draw the background of the round rectangle. (clipview == view that gets clipped)
Then, I call the following code to do the CAShapeLayer manipulation:
//0. CLIP
shapeLayer = [CAShapeLayer layer];
CGRect shapeRect = self.bounds;
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake((shapeRect.size.width)/2.0f, (shapeRect.size.height)/2.0f)];
[shapeLayer setFillColor:[[UIColor colorWithRed:0 green:1 blue:0 alpha:1.0f] CGColor]];
[shapeLayer setStrokeColor:[[UIColor redColor] CGColor] ];
[shapeLayer setLineWidth:1];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setOpacity:1];
CGRect shapeLayerPathRect = CGRectMake(0, 0, self.bounds.size.width, 44.0f);
shapeLayer.path = [self roundRectPathForRectangle:shapeLayerPathRect andRadius:CORNER_RADIUS];
[[self.clipView layer] setMask:shapeLayer];
//1. draw the shaded outline of a round rectangle
outlineLayer = [self topDownShadowShapeLayer]; //setup for my CAShapeLayer, involving shadow stuff, line color, etc
outlineLayer.path = [self roundRectPathForRectangle:shapeLayerPathRect andRadius:CORNER_RADIUS];
[self.clipView.layer addSublayer:outlineLayer];
Although the animation the proceeds is the quickest approach (stretching both CAShapeLayers), the entire element (MyCustomView) causes everything to be a lot slower! For example, UIView animations that go on, also when MyCustomView is inside a ScrollView it is VERY jumpy.
I'm mostly concerned about the UIScrollView problem. Why is it so choppy for a UIScrollView to translate these layers around? Is there a good solution / what's another approach that can achieve the same effect?

Getting the CGContextRef from a subview

I'm using UIGraphicsGetCurrentContext() to create a gradient background for a UI element, but I guess I misunderstood where the drawing was taking place. I wanted the drawing to take place on a subview, but instead it's happening in the view itself.
I think the problem is that when I use UIGraphicsGetCurrentContext(), I'm getting a CGContextRef of the view, so that's where the drawing is taking place. What I want to do is have the drawing take place on the subview, so that I can fade it in and out with other related subviews. Can this be done, or do I need to create another UIView subclass just for the background layer?
Here's a simplification of the code I'm using and my goal is to be able to fade in and out the background gradient in topBar while leaving the InterfaceControlsView view visible.
#implementation InterfaceControlsView
- (id)initWithFrame:(CGRect)frame
{
topBar = [[UIView alloc] initWithFrame:CGRectMake(0.0, 20.0, self.frame.size.width, 45.0)];
/* etc. */
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = topBar.frame; // topBar is a subview
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
/* etc. */
}
#end
To create gradient bg for subviews, you dont need to create subclass, use gradiant layers.
Hope this helps
CALayer *layer = _button.layer;
layer.borderWidth = 1.0f;
layer.borderColor = [UIColor lightGrayColor].CGColor;
CAGradientLayer *gLayer = [CAGradientLayer layer];
[gLayer setName:#"gradient"];
gLayer.frame = layer.bounds;
gLayer.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:26.0/255.0 green:94.0/255.0 blue:74.0/255.0 alpha:1.0].CGColor,
(id)[UIColor colorWithRed:23.0/255.0 green:59.0/255.0 blue:37.0/255.0 alpha:1.0].CGColor,
nil];
[layer addSublayer:gLayer];

UIView animation to slide down

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){ }];

Adding shadow to a UINavigationControllerer

I'm trying to add shadow to a UINavigationController. My have is based on NavController and i'm trying to add a shadow like in game centers. I have this so far. It works n a UINavigationnBar but i'm trying to get it to work throughout the entire app.
CGColorRef darkColor = [[UIColor blackColor] colorWithAlphaComponent:.5f].CGColor;
CGColorRef lightColor = [UIColor clearColor].CGColor;
CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
newShadow.frame = CGRectMake(0, 44, 320, 10);
newShadow.colors = [NSArray arrayWithObjects:(id)darkColor, (id)lightColor, nil];
CALayer *superlayer = self.newShadow.superlayer;
[self.newShadow removeFromSuperlayer];
[superlayer addSublayer:self.newShadow];
[self.navigationBar.layer addSublayer:superlayer];
It works directly on a UINavigationBar but applying to a NavigationController project it fails. It builds but won't add the shadow. Any ideas?
EDIT:
I have been trying different approaches to this. I successfully created the gradient by using a shape.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIImage *backgroundImage;
backgroundImage = [UIImage imageNamed:#"nav.png"];
[backgroundImage drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 1.0, 0.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.5 };
CGGradientRef myGradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, num_locations);
CGPoint myStartPoint, myEndPoint;
myStartPoint.x = 0.0;
myStartPoint.y = 0.0;
myEndPoint.x = 0.0;
myEndPoint.y = 54.0;
CGContextDrawLinearGradient (context, myGradient, myStartPoint, myEndPoint, 1);
}
I can't get the gradient below the UINavigationBar and its overlaying the image. I can't seem to get this to work. What i'm trying to do is add the same shadow Game Center has. I have tried a few different ways. All I need to do here is get this to lie underneath the UINavigationBar allowing the image to be on top and have a little part of the shadow lie on top on the UITableView so when you scroll up its above the UITableView. If you fire up Game Center you'll see exactly what i'm talking about.
I believe you should add it to the
UINavigationController.view.layer
As the UINavigationController he is not a UIView child.
if already you did so, an other way to effect the navigationbar, consistently all over the app is to use the UINavigationBarCategory:
you sould pace this at the end of your delegate - after the #end
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
//implement your design
}
#end
EDIT
in this link you can find basic info on how to draw the bar:
An iPhone Graphics Drawing Tutorial using Quartz 2D
and here you can find how to add a shadow:adding shadow with quartz 2d
Good luck
You could subclass UIView and use your existing code to draw the shadow by overriding -drawRect:. Then wherever its needed, create it and add it to the view of the navigation controller.
Consider this example
CGFloat screenWidth = [UIScreen mainScreen].applicationFrame.size.width;
ShadowView *shadowView = [[ShadowView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, screenWidth, 20.0f)];
[self.navigationController.view insertSubview:shadowView atIndex:0];
By adding it to the view of the navigationController at index 0, you're effectively overlaying it on top of whatever the view controller is displaying. You could further improve this by checking for the scale of the main screen to appropriately calculate the height of the shadow view so that it will appear correctly on all devices regardless of having a retina display.

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