Code

Code snippet - cropping a screenshot

Recently I have been having fun with the UIView drawViewHierarchyInRect: method (yes, my life is one big party). It is a useful way of producing images within an app for use as backgrounds, with social media posts etc.

Doco

The method provides an image of the full view, but this is not always what you want. For example you might want to crop the image to remove controls, status bars etc before using it. So how to crop the image?

By the way, don't be misled by the "InRect" in the method name. It refers to the location of the drawing rather than an area of the view to be rendered. So drawViewHierarchyInRect:croppingRect … will not crop the image as you might hope, but will draw an image of the entire view, and draw it in the specified rectangle.

The code below is one way to capture and crop an image of a view.

Code

The cropping rectangle is specified in the coordinate system of the view which leads to the one subtlety here. The operation to crop the image (CGImageCreateWithImageInRect) works on pixels rather than points, so the cropping rectangle needs to be scaled by the scale factor of the display (2 in the case of retina devices).

(If you are thinking that the scaling operation lacks a bit of CS cred feel free to use CGRectApplyAffineTransform and CGAffineTransformMakeScale, but also perhaps think about getting out more)

Update

on 2014-03-20 12:58 by John

The code above comes with one warning (other than "use at your own risk" obviously) - do not use it anywhere in the initial display of the application delagate window's root view controller view, at least not without one small change.

The issue is the "drawViewHierarchy ... afterScreenUpdates:YES". If you invoke this while loading the root view controllers view from application:didFinishLaunchingWithOptions: you will end up with the "Application windows are expected to have a root view controller at the end of the application launch" error and a blank screen.

This is hard to track down because the root view controller is set, and the view is loaded. It all looks like it should be OK but the afterScreenUpdates:YES causes the view controller and it's view not to be correctly recognised and handled.

If you need to use code like this at launch, change it to "afterScreenUpdates:NO" and all should be well.

Quirk of the day - UIButton states and Images

UIButtons and I don't seem to get on. Embarrassing but true. Controls don't come much simpler than a button, but I still have spent an inordinate amount of time wrestling with them.

My latest episode of button distraction was trying to get a button to show different images in two states. Simple, right? Done it many times before. You just set different images for different states and toggle the states. I traditionally use the selected state for this.

First attempt

That works fine ... until I disabled the button while selected. At that point the button showed a greyed-out version of the unselected image. When enabled again the selected image came back.

So I spent some time making sure that I wasn't toggling the selected state myself as part of the disable/enable logic. No that wasn't the problem.

Time for something new and radical - I reread the documentation and thought about was what was going on.

As often happens the reason for the behaviour is simple and obvious ... disabled is just another state and it gets added (or'ed actually) into the button's state. Disabled | selected is not the same as selected so my explicit selected image was not used.

The obvious solution was to set the same image for selected | disabled as well. As with many obvious solutions, it was wrong.

Second attempt

Now when I disabled the button I got the right image, but it wasn't greyed out in the normal manner for disabled buttons! Apparently greying-out the image is only the behaviour when an image is not specified for the disabled state and the normal state image is being used.

Of course this makes sense. In most cases when you explicitly specify a disabled image you want to use that image as is, not have a greyed-out version forced upon you.

I could see two alternatives - (1) create a greyed-out version of my selected icon for use when disabled, or (2) abandon using the selected state mechanism completely and set the image for the normal state as required and let the default disable behaviour grey them out.

Unsatisfactory result

Fiddling around with another icon image and getting it to match the system generated greyed-out icons seemed like a lot of work and destined to not look quite right, so I went with option 2.

Not a particularly satisfying resolution as instinctively it feels a bit clunky, but it works and I now know a little more about button behaviour than I did.

Quirk of the day - UIWindow rootViewController

Opening up 'old' code is always fun. This quirk is something I came across looking at some code I wrote in 2011. The code is in the App Store and works fine, but when I opened it up in Xcode and compiled it screen rotation was glitchy and I got the warning:

Application windows are expected to have a root view controller at the end of application launch

Fair enough. I certainly thought I had a root view controller by the end application launch. When I checked in application:didFinishLaunchingWithOptions: in the app delegate I could see the view controller being created and it's view being added as a subview of the window's view:

    _rootViewController= [[OSRootViewController alloc] init];
    [self.window addSubview:[_rootViewController view]];

Now "This is how I have always done it" and it seems to have been fine. I checked back in one of the Big Nerd Ranch books which had been my introduction to iOS coding, and they were using the same pattern.

Obviously the world has moved on and something more is needed. If in doubt, read the doco. In the UIWindow documentation I found the rootViewController property. Sure enough, assigning my view controller to this property (rather than adding the view controller's view as a subview) did the trick:

    self.window.rootViewController= _rootViewController;

Mystery solved. Yet another one of those little time consuming things to chase down.

Happy coding.