Categories
Tutorials

How Instruments can be used to fix a graphics performance issue

Lately, I have been investigating an issue a customer of mine’s app showed.

My customer’s app is a sort of PDF viewer that also allow to add annotations. Such annotations are not stored in the PDF itself, instead they are managed in a custom way and drawn on top of the PDF in a dedicated CATiledLayer-based view.

The issue was that after a couple of zoom-in/out operations, or alternatively after moving from one PDF to another, the CPU used to jump to 100% usage, even though no apparent operation was ongoing. This hampered a lot the overall experience of the app, since practically all graphics operations became extremely slow with the app stuck in that state. Curiously, other kind of operations, e.g., downloading a file, were not slowed down significantly.

The issue had quite a trivial cause, due to some “bad” programming (meaning that some obvious rule was not respected), but the interesting part in this is how I came to understand what was going on.

Instruments was the main tool that came to rescue, as you can imagine. The picture at the left shows the CPU Profiler tool output. You can see how the overall CPU usage goes to 100% at some point and stays there. The fundamental bits of information one can get from this output are the following:

  • there was something going wrong in the cleanup phase of a pthread lifecycle; knowing that the CATiledLayer used for optimised drawing uses threads, this was a hint at that something was not handled correctly in the drawing phase; hard to think at some CATiledLayer bug, but still a possibility;
  • furthermore, (while the program was running) the “self” field showed that there were very many calls being made to practically all symbols under “pthread_dst_cleanup” and that those calls would not halt for any reason;
  • among the calls being made repetitively, my attention got caught by those to FreeContextStack/PopContext/GetContextStack.

The last point was the key to understand that something in the handling of the Core Graphics context stack was not doing correctly. So I set up to investigate the custom drawing code and indeed what I found was a couple of unbalanced calls to UIGraphicsPushContext and UIGraphicsPopContext. Fixing this, removed the CPU utilisation issue.

As I said, the issue was caused by incorrect programming, but nevertheless catching it was an interesting experience.

Categories
Tutorials

iOS6: dynamic autorotation

One of the most intrusive changes brought by iOS6 is the way autorotation is handled in UIViewControllers. If you have an autorotating app for iOS5, you will need to change it to correctly support autorotation under iOS6. If you develop an app which is supposed to run both on iOS5 and iOS6, then you will have to handle autorotation in the old as well as the new way.

In this post, I am going to provide a simple solution to a problem which, as much as I have been seeing around me, has not an entirely trivial solution. The problem statement is the following: a view which is allowed to autorotate only under certain conditions; otherwise, it will be frozen (as far as autorotation is concerned).

E.g., you take a screenshot of your UI at a given moment, then display it, maybe applying some effects to it. If the device is rotated in this context, your overall UI will rotate accordingly, while the snapshot you took will not (it will still reflect the initial device orientation). Now, what you want is freezing autorotation while the snapshot is shown. Another example: on top of your fully “elastic” UI, you display some piece of information which is not meant to autorotate. Again, what you want is freezing autorotation while that piece of information is displayed.

Under iOS5, this was really straightforward, because each time an autorotation event is detected, UIkit sends your controllers the shouldAutorotateToInterfaceOrientation: message. There you have a chance to deny autorotating to a specific interface rotation according to your criteria.

Under iOS6, it is equally straightforward except for the unlucky naming of the cornerstone iOS6 autorotation method, namely shouldAutorotate. What that name leads you (or at least me) into thinking is that you can decide there whether (and when) your view can autorotate. Wrong. The shouldAutorotate method does actually respond to an optimisation purpose: if your view controller shouldAutorotate returns NO, then the framework will not forward any autorotation messages to it.

So, if you want to control the conditions under which your controllers autorotate, you will have to either leave that method undefined or define it as to always return YES. The real “meat” of automation control is thus given in the supportedInterfaceOrientations method. E.g., it could be defined as:

[sourcecode]

– (NSUInteger)supportedInterfaceOrientations {

if ([self canAutorotateNow])
return UIInterfaceOrientationMaskAll;

if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
return UIInterfaceOrientationMaskLandscape;
return UIInterfaceOrientationMaskPortrait;
}
[/sourcecode]

You see, the idea is checking if the autorotation is frozen at the moment supportedInterfaceOrientations is called; if it is, then only return as a supported orientation the one corresponding to the current status bar orientation.

Categories
Tutorials

How to give UIWebView Rounded Corners and a Shadow

If you would like to give your UIWebView rounded corners, you can find several recipes on the web, all ending up in this code snippet:


webView_.layer.cornerRadius = 10;
webView_.clipsToBounds = YES;

This will produce a nice rounded corner web view like in the picture below.

Everything wonderful. Now, what about a shadow below the web view? Would not it be even more beautiful? So, let’s tweak further the CALayer associated to the web view so that we can get a shadow almost for free (courtesy of Mike Nachbaur):


webView_.layer.shadowColor = [UIColor blackColor].CGColor;
webView_.layer.shadowOpacity = 0.7f;
webView_.layer.shadowOffset = CGSizeMake(10.0f, 10.0f);
webView_.layer.shadowRadius = 5.0f;
webView_.layer.masksToBounds = NO;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:_label.bounds];
webView_.layer.shadowPath = path.CGPath;

Anyway, the result is not as expected:

Ahemmm… the shadow did away with the rounded corners…

The trouble lies with the CALayer‘s `masksToBounds` option conflicting with the UIWebView‘s`clipsToBounds`…

What comes to the rescue is the fact that `UIWebView` is just a web-wrapper around a `UIScrollView` which is in charge of displaying the rendered content… this is where the rounding has to be done actually. So let’s try it this way:


webView_.layer.cornerRadius = 10;
for (UIView* subview in webView_.subviews)
subview.layer.cornerRadius = 10;

webView_.layer.shadowColor = [UIColor blackColor].CGColor;
webView_.layer.shadowOpacity = 0.7f;
webView_.layer.shadowOffset = CGSizeMake(10.0f, 10.0f);
webView_.layer.shadowRadius = 5.0f;
webView_.layer.masksToBounds = NO;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:_label.bounds];
webView_.layer.shadowPath = path.CGPath;

Now the result is what was to be hoped…