iOS: Poor performance with CAGradientLayer - ios

I'm using a class method to return a CAGradientLayer and insert it into a sublayer in a view. If I set the frame to a quarter of the screen size, width and height, my application runs smooth as butter, but most of the time if I set the frame size to the full width and height, the smoothness of the animation takes a big hit.
I found that most of the time when the animation smoothness is ruined, I can load in a large set of images, and even though the CAGradientLayer still exists, the smoothness is restored. This makes me think that the act of loading in images triggers the OS to empty some kind of cache. Is there anything that comes to mind that can be emptied manually?
Would somehow setting the size of the CAGradientLayer to something small and then stretching it out help even if I was to lose quality?
Before the CAGradientLayer is returned, I also tried setting these properties with little or no effect to the problem:
headerLayer.shouldRasterize = YES;
headerLayer.rasterizationScale=[UIScreen mainScreen].scale;
headerLayer.drawsAsynchronously = YES;
headerLayer.opaque = YES;

Related

Draw order using CAGradientLayer & drawRect:(CGRect)

My view overrides the drawRect method to render graphics.
And I've recently added a gradient background using CAGradientLayer and [view.layer insertSubLayer: atIndex:0]
However the CAGradientLayer gets drawn over my graphics instead of underneath.
Setting alpha of the gradient colours 0.5 shows that my graphics are still being drawn.
This is an app with a high graphics refresh rate, I cannot afford to redraw a gradient on every refresh so was counting on the CAGradientLayer's backing store to keep things performant.
How should I approach this?
Thank you!
Sublayers are always drawn on top of the base layer, much like subviews are always drawn on top of the base view.
What you could try is work at the superview's level, and add a gradient sublayer to it. It will be displayed below your view.

iOS animate clipToBounds, masksToBounds, or similar

I have an image view that starts out cropping it's image with clipsToBounds and content mode set as "scale aspect fill", and I want it to "enlarge" the image to the whole image. If clipsToBounds=NO was an animatable property, that would be exactly what I want, which it does not seem to be. Is there a way to animate that?
If not, another way would be resizing the view so it is the same width-height ratio as the image's size, while keeping it no smaller than the image view was to begin with (i.e. minimal increate to height or width, no decrease to either). I'm not sure the best approach to doing this, considering the image could be much bigger or smaller than the image view, and the image's width-height ratio could be just about anything (but it will usually be an iOS device camera photo).
UPDATE 1: It seems like layer.masksToBounds would work, the documentation says that it is animatable, but my code does not seem to work:
CABasicAnimation *layerAnim = [CABasicAnimation animationWithKeyPath:#"masksToBounds"];
layerAnim.fromValue = #(YES);
layerAnim.toValue = #(NO);
layerAnim.removedOnCompletion = YES;
layerAnim.duration = 1.0;
[_imageView.layer addAnimation:layerAnim forKey:#"masksToBounds"];
I am running this layer animation at the same time as a UIView block animation that is changing the frame and transform of the image view, if that matters.
UPDATE 2: I did not have this core animation in the UIView animation block, which according to the documentation, it should be. I have moved the above code into the animation block, but it is still not animating (change happens instantly). I'm beginning to think that "animatable" simply means it can be placed in an animation, not that it will actively animate over time.
So you don't want your image to grow, but you want it to be clipped at first, and then the outer pixels are exposed?
You can do that using Core Animation and a layer mask.
Here's what you do:
Set the image view's clipsToBounds to FALSE, so the image would fully display if you let it.
Create a CAShapeLayer that's the size of the whole image. Create a rectangular bezier path that's the size of the initial image. Install that bezier path's CGPath as the path of the shape layer.
Set the shape layer's fill color to an opaque color
Then install the shape layer as the mask of your image view's layer. That will cause the image to be clipped to the shape of the shape layer.
Now, if you change the path that's installed in the shape layer to a rectangle that's the full size of the image, the system will animate the change for you.
As of March 21st 2019, it seems like the masksToBounds documentation is just wrong. masksToBounds does not seem to be animatable. I have tried animating masksToBounds with CABasicAnimation and CAKeyframeAnimations and it doesn't seem to work.

CoreGraphics (drawRect) for drawing label's and UIImageView in UITableViewCell

I have a UITableViewCell in which inside it I have 5 UILabel a UIButton and a UIImageView that fills out the cell as background. The performance seems to be a bit slow, so I was thinking of using CoreGraphics to improve it. Is it true that using CoreGraphics instead of UILabel as subViews will make things much faster? If yes why?
I have the following code to draw shadow on the cells:
[self.layer setBorderColor:[UIColor blackColor].CGColor];
[self.layer setShadowColor:[UIColor blackColor].CGColor];
[self.layer setShadowRadius:10.0];
[self.layer setCornerRadius:5.0];
[self.layer setShadowPath:[[UIBezierPath bezierPathWithRect:self.frame] CGPath]];
In general, (as Gavin indicated) I would say that you have to first confirm that the subviews are indeed causing a jitter in your scrolling.
When I'm testing UITableViewCell scrolling performance, I often use the Time Profiler in Instruments. Switch to Objective-C Only in the left-hand panel, and look at what is taking the most time on your Main Thread. If you see lots of time spent on rearranging (layout) or drawing of subviews, you may need to use CoreGraphics. If the time instead is spent on allocation/deallocation, then you may want to examine how your subviews are reused if at all. If they are not being reused, then of course this can cause performance problems.
Then of course, you should look at compositing. If your subviews are not opaque (identify this through the CoreAnimation instrument), then the performance may be seriously impacted.
Also of note -- realize that shadows are expensive to draw, and depending on your implementation, they may be redrawing on every frame! Your best option is to make sure that any CALayer shadows are fully rasterized, and have a path defined so live computations from the pixel mask don't have to be made.
If finally, you identify that the layout and redrawing of each subview individually is causing the slowdown, then I have a couple of points/explanations:
Your implementation of the drawing routine for your table view cell will probably be slower than the highly optimized drawing that Apple has written for its views. So you won't win any battles re-implementing drawing of UIImageView itself. Performance gains instead come from two places when drawing with CoreGraphics: a.) Pre-rendering of previously non-opaque views, and reduction of time spent in the layout phase of the view drawing cycle - this reduces the workload on the GPU/CPU when scrolling. b.) Reduction in time switching CG contexts for individual view drawing. Each element now draws into the same graphics context at the same time, reducing switching costs.
Drawing in drawRect using CoreGraphics on the main thread draws using the CPU, and depending on how complex your cells are, this may cause jitters of its own. Instead, consider drawing in a background thread to a separate CGContext, then dispatching a worker to insert the contents of the drawing as a CGImageRef into a CALayer, or as a UIImage in a UIImageView. There is a naive implementation on GitHub: https://github.com/mindsnacks/MSCachedAsyncViewDrawing
If you do decide to go with background CoreGraphics drawing, be warned that at present (December 2012), I believe there is a bug in the NSString drawing categories when drawing on background threads that results in a fallback to webkit which is absolutely not thread safe. This will cause a crash, so for the present time, make sure that the asynchronous drawing is done in a serial GCD/NSOperation queue.
On the simulator, Debug→Color Blended Layers. Red is bad, green is good.
More accurately, red means that the GPU needed to do an alpha blend. The main cost is in the memory bandwidth required to draw the pixels twice and possibly re-fetch the extra texture. Completely transparent pixels are really bad.
Three fixes (all of which reduce the amount of red), which you should consider before diving into Core Graphics:
Make views opaque when possible. Labels with the background set to [UIColor clearColor] can often be set to a flat colour instead.
Make views with transparency as small as possible. For labels, this involves using -sizeToFit/-sizeThatFits: and adjusting layout appropriately.
Remove the alpha channel from opaque images (e.g. if your cell background is an image) — some image editors don't do this, and it means the GPU needs to perform an alpha test and might need to render whatever's behind the image.
Additionally, turn on and Color Offscreen-Rendered (possibly after turning off Color Blended Layers so it's easier to see). Offscreen-rendered stuff appears in yellow, and usually means that you've applied a layer mask. These can be very bad for performance; I don't know if CoreAnimation caches the masked result.
Finally, you can make CoreAnimation rasterize the cell by setting cell.layer.shouldRasterize = YES (you might also need cell.layer.rasterizationScale = [[UIScreen mainScreen].scale on retina devices; I forget if this is done automatically). The main benefit is that it's easy, and it's also more efficient than rendering the image view yourself in Core Graphics. (The benefit for labels is reduced since the text needs to be rendered on the CPU.)
Also note that view animations will be affected. I forget what setting CALayer.shouldRasterize does (it might re-rasterize it every frame of the animation, which is a bit wasteful when it'll only be drawn to screen once), but using Core Graphics will (by default) stretch the rendered content during the animation. See CALayer.contentsGravity.
What evidence do you have to suggest that what you have in the views is causing the performance issues? This is a deep black hole that can suck you in, so be sure you know the problem is where you think it is.
Are you preloading all your data? Have you pre-downloaded the images? What you're describing shouldn't be causing a slow down in UITableViewCell. Apple developers are much smarter than you and I so make sure you've got the data to back up your decision!
I've also seen a lagging UITableViewCell within the simulator with no noticeable difference on real hardware.
It is true that using CoreGraphics can speed up your draw performance but it can also slow it down if you do it wrong! Have a look at the Apple Tutorial on Advanced Table View Cells for how to perform the technique.

iOS - Slow animation on iPhone 4S (but fine on simulator)

I have subclassed a UILabel with the following code, which works fine - but any animations involving the subclass runs a lot slower than normal UILabels. I'm assuming Quartz is the culprit, but is there anything I could do to speed things up a bit?
- (void)drawTextInRect:(CGRect)rect
{
CGSize shadowOffset = self.shadowOffset;
UIColor *textColor = self.textColor;
// Establish the Quartz 2D drawing destination:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1);
CGContextSetLineJoin(context, kCGLineJoinRound);
// Draw the label’s outline:
CGContextSetTextDrawingMode(context, kCGTextStroke);
self.textColor = [UIColor whiteColor];
[super drawTextInRect:rect];
// Draw the label:
CGContextSetTextDrawingMode(context, kCGTextFill);
self.textColor = [UIColor textColor];
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
self.shadowOffset = shadowOffset;
}
What #MobileOverlord said is certainly applicable, especially the parts about about profiling.
I will note that setting shouldRasterize=YES is not a catch-all solution (why wouldn't Apple have set it on as default if that were the case?). Yes, it can improve scrolling performance, but it can do so at the expense of memory use since you can end up with a bunch of large images sitting around in cache.
It also incurs overhead at the time of creation, I believe (but would have to check to be sure) including an off-screen rendering pass to actually create the rasterized copy. Depending on how the layer is used, this could actually hurt performance.
An additional factor to consider is whether your view has any transparency. If you can guarantee to the frameworks that your view is opaque (cf. setOpaque/isOpaque), they can optimize rendering by not considering all the complexities associated with alpha channels, etc. Similar considerations apply to CALayer.
Finally, outside the block of code you showed, did you do anything sneaky to the backing layer (e.g. set a shadow or corner radius)? That's a quick way to kill performance on animation too.
After you are finished drawing your label you can call shouldRasterize on it's layer and it should speed up your animation.
shouldRasterize A Boolean that indicates whether the layer is rendered
as a bitmap before compositing. Animatable
#property BOOL shouldRasterize Discussion When the value of this
property is YES, the layer is rendered as a bitmap in its local
coordinate space and then composited to the destination with any other
content. Shadow effects and any filters in the filters property are
rasterized and included in the bitmap. However, the current opacity of
the layer is not rasterized. If the rasterized bitmap requires scaling
during compositing, the filters in the minificationFilter and
magnificationFilter properties are applied as needed.
When the value of this property is NO, the layer is composited
directly into the destination whenever possible. The layer may still
be rasterized prior to compositing if certain features of the
compositing model (such as the inclusion of filters) require it.
The default value of this property is NO.
From CALayer Class Reference
The simulator is always going to give you way better results than a device will because it is able to use the full processing power and memory of your system. You'll usually get flawed results this way. Whenever you are doing CoreGraphics drawing in conjunction with CoreAnimation it is important to test the results on a real device.
For this you can try to run your app in Instruments Core Animation Tool to try to find culprits. Check out my tutorial on it.
Instruments – Optimizing Core Animation

Does shouldRasterize on a CALayer cause rasterization before or after the layer's transform?

I'm attempting to optimize my app. It's quite visually rich, so has quite a lot of layered UIViews with large images and blending etc.
I've been experimenting with the shouldRasterize property on CALayers. In one case in particular, I have a UIView that consists of lots of sub views including a table. As part of a transition where the entire screen scrolls, this UIView also scales and rotates (using transforms).
The content of the UIView remains static, so I thought it would make sense to set view.layer.shouldRasterize = YES. However, I didn't see an increase in performance. Could it be that it's re-rasterizing every frame at the new scale and rotation? I was hoping that it would rasterize at the beginning when it has an identity transform matrix, and then cache that as it scales and rotates during the transition?
If not, is there a way I could force it to happen? Short of adding a redundant extra super-view/layer that does nothing but scale and rotate its rasterized contents...
You can answer your own question by profiling your application using the CoreAnimation instrument. Note that this one is only available in a device.
You can enable "Color hits in Green and Misses Red". If your layer remains red then it means that it is indeed rasterizing it every frame.

Resources