iOS Screen Rotation Degraded When Shadows Are Visible - ios

This isn't in reference to any particular code, but I've noticed that when I have a UIView that has a shadow added to it's layer, the animation when rotating between interface orientations becomes much more laggy/choppy.
Has anyone noticed this issue or found a workaround?

When using shadows, the shadowPath property of CALayer makes a very (!) noticable difference in performance, especially on the New iPad. Although I agree that disabling shadows when changing the display orientation is a good idea, it may be worth a try to just use shadowPath (if you do not use it already). Although the path can be any valid CGPathRef, in most cases this is what you want:
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;

There are things which are very expensive in terms of CPU time. Check it out in Instruments some time.
shadows
bezier paths
bezier paths with dashes (really expensive)
Thats not a comprehensive list. I suspect gradients will be there too.
If you find these things are degrading your animation or redraw you will need to toggle them in the UIViewController methods.
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//disable shadows + expensive drawing
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
//enable shadows + expensive drawing
}

Related

Best way to handle autoresizing of UIView with custom drawing

I'm working on a custom view, that has some specific Core Graphics drawings. I want to handle the view's autoresizing as efficiently as possible.
If I have a vertical line drawn in UIView, and the view's width stretches, the line's width will stretch with it. I want to keep the original width, therefore I redraw each time in -layoutSubviews:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// ONLY drawing code ...
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self setNeedsDisplay];
}
This works fine, however I don't think this is a efficient approach - unless CGContext drawing is blazing fast.
So is it really fast? Or is there better way to handle view's autoresizing? (CALayer does not support autoresizing on iOS).
UPDATE :
this is going to be a reusable view. And its task is to draw visual representation of data, supplied by the dataSource. So in practice there could really be a lot of drawing. If it is impossible to get this any more optimized, then there's nothing I can do... but I seriously doubt I'm taking the right approach.
It really depends on what you mean by "fast" but in your case the answer is probably "No, CoreGraphics drawing isn't going to give you fantastic performance."
Whenever you draw in drawRect (even if you use CoreGraphics to do it) you're essentially drawing into a bitmap, which backs your view. The bitmap is eventually sent over to the lower level graphics system, but it's a fundamentally slower process than (say) drawing into an OpenGL context.
When you have a view drawing with drawRect it's usually a good idea to imagine that every call to drawRect "creates" a bitmap, so you should minimize the number of times you need to call drawRect.
If all you want is a single vertical line, I would suggest making a simple view with a width of one point, configured to layout in the center of your root view and to stretch vertically. You can color that view by giving it a background color, and it does not need to implement drawRect.
Using views is usually not recommended, and drawing directly is actually preferred, especially when the scene is complex.
If you see your drawing code is taking a considerable toll, steps to optimize drawing further is to minimize drawing, by either only invalidating portions of the view rather than entirely (setNeedsDisplayInRect:) or using tiling to only draw portions.
For instance, when a view is resized, if you only need to draw in the areas where the view has changed, you can monitor and calculate the difference in size between current and previous layout, and only invalidate regions which have changed. Edit: It seems iOS does not allow partial view drawing, so you may need to move your drawing to a CALayer, and use that as the view's layer.
CATiledLayer can also give a possible solution, where you can cache and preload tiles and draw required tiles asynchronously and concurrently.
But before you take drastic measures, test your code in difficult conditions and see if your code is performant enough. Invalidating only updated regions can assist, but it is not always straightforward to limit drawing to a provided rectangle. Tiling adds even more difficulty, as the tiling mechanism requires learning, and elements are drawn on background threads, so concurrency issues also come in play.
Here is an interesting video on the subject of optimizing 2D drawing from Apple WWDC 2012:
https://developer.apple.com/videos/wwdc/2012/?include=506#506

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.

UIScrollView Performance Tips?

I have a UIScrollView that is animating a lot of UIViews (probably too many). Also, these UIViews represent "pages", and sometimes multiple pages are stacked on top of each other, resulting in a "pile" of pages (setup by adding subviews to a given view).
I know that scrolling an excessive number of UIViews can have poor performance, but I was wondering if anybody had some general tips for me to improve performance?
For now, doing drawing manually in drawRect is not something I would like to consider because it would mess up various "page pile" animations. I will keep it in mind for a last resort, but I'd definitely like to avoid it if possible.
Update:
The cause of the performance hit has been determined and is two fold: I'm using antialiasing and shadows on all my UIViews. When I toggle them both off, the performance issues are resolved! However, I obviously don't want to just toggle them off :)
I'm creating my shadows like so:
self.imageView.layer.opaque = YES;
self.imageView.layer.masksToBounds = NO;
self.imageView.layer.shadowOffset = CGSizeMake(-4, 0);
self.imageView.layer.shadowRadius = 2.5;
self.imageView.layer.shadowOpacity = 0.15;
self.imageView.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(
self.bounds.origin.x,
self.bounds.origin.y,
self.bounds.size.width + 8,
self.bounds.size.height + 2)].CGPath;
Any tips to improve the performance?
As far as antialiasing, it is almost a necessity. The problem is that those "offset pages" are slightly rotated AND my pages have a 1 pixel border. Slightly rotated with a 1 pixel border without antialiasing looks awful. I am simply enabling antialiasing in the .plist by setting "Renders with Edge Antialiasing" to YES.
Any suggestions on how to improve my shadow/antialiasing performance would be appreciated.
How many UIViews are "a lot"? And what kind of views are we talking about?
100 UIViews usually aren't a problem, if they don't require complex drawing.
However, 10 UIWebView instances rendering PDFs are a different story...
Make sure your views are only laid out and drawn if necessary (= only if they're actually visible). You can check this by creating breakpoints in the view's layoutSubviews, for example.
Also, use opaque UIView elements whenever possible. This makes drawing more efficient, as views below opaque elements don't have to be drawn.
If you happen to have custom CALayer instances (e.g. CAShapeLayer with shadows etc.) that require a lot of processing but rarely change, you might want to consider enable rasterization on those: yourLayer.shouldRasterize = YES;
This accelerates drawing by caching the rendered composite image.

How to Efficiently Draw Shadows on Numerous CALayers in iOS?

I've got some card games which use CALayers to draw individual cards. There can easily be 40 or 50 of them on the screen, which usually works fine.
I recently tried to turn on their shadows using the simple properties for CALayers:
theCardLayer.shadowOffset = CGSizeMake(3,2);
theCardLayer.shadowOpacity = 0.7f;
At that point, the program started getting really laggy. Fair enough; some of the docs said that the shadows could be CPU-intensive.
Any ideas for how to efficiently draw shadows on everything? They're all on the same CALayer in the same UIView, so I'm wondering if there might be a way to pull the mask of the layer or its UIView and shadow that, or something ...
Any functionality up to iOS5 is fair game.
At the very least, try setting your layer's shadowPath property. It can make shadow rendering significantly faster.
Kurt offered up the correct solution. Here's an example of how to use a shadowPath:
UIBezierPath *thisCLPath = [UIBezierPath
bezierPathWithRoundedRect:theCardLayer.bounds
cornerRadius:10.0f];
theCardLayer.shadowPath = thisCLPath.CGPath;
Clearly, I'm using rounded corners here. For a straight-edged layer, you can just use bezierPathWithRect:. There are a few other helpful methods in UIBezierPath as well.
The result is just the right side of laggy on older iOS devices (like an iPhone4 or a mid-generation iPod Touch) and blazing on an iPad3.

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