Andy Heydon

Month: December, 2012

iOS6 rotation addendum

I wrote previously about handling rotation in iOS6, notably with respect to modal dialogs which I determined are best handled as child view controllers. I recently extended the app that brought to light a situation I wasn’t handling and therefore I need to add an addendum to that post.

The new feature I added was a full screen modal that I did not want to be rotated on the iPhone. The layout was primarily data entry and hence a keyboard would over a significant portion of the screen, so the flow really dictated a portrait only display. I had to tweak the code to prohibit the rotation on this particular view controller.

A very quick reading of the documentation would suggest that adding the following code to the modal view controller would do the job.

- (BOOL)shouldAutorotate {
return IPAD ? YES : NO;
}
- (NSUInteger)supportedInterfaceOrientations {
return IPAD ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskPortrait;
}
view raw gistfile1.m hosted with ❤ by GitHub

We are saying that this view only supports portrait on the iPhone and that we should not auto-rotate. However this does not work as expected, because under iOS6 container view controllers (such as UINavigationController) do not consult their children about rotation events. The root view controller application in my app is a UINavigationController and so the determination as to rotate or not does not go deeper into the view hierarchy and therefore the autoRotate and supportInterfaceOrientations methods on my modal view controller are never called. I am using a base UINavigationController so the rotation methods fall back to their defaults, which on the iPhone is to allow everything except upside down portrait.

The first step is to force the UINavigationController consult its top level view controller about rotation, which is easily managed by a category:

@implementation UINavigationController (Rotation)
// iOS 6
- (BOOL)shouldAutorotate {
return [[self topViewController] shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations {
return [[self topViewController] supportedInterfaceOrientations];
}
@end
view raw gistfile1.m hosted with ❤ by GitHub

My top level view controller is also a container that presents modals as child view controllers, so it needs to consult its children too.

- (BOOL)shouldAutorotate {
BOOL autoRotate = YES;
for (UIViewController *vc in [self childViewControllers]) {
autoRotate = autoRotate && [vc shouldAutorotate];
}
return autoRotate;
}
- (NSUInteger)supportedInterfaceOrientations {
NSUInteger supported = IPAD ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskAllButUpsideDown;
for (UIViewController *vc in [self childViewControllers]) {
supported &= [vc supportedInterfaceOrientations];
}
return supported;
}
view raw gistfile1.m hosted with ❤ by GitHub

So finally this code will ask the modal view controller about rotation and will receive an appropriate answer. This new modal will prohibit rotation, but other simpler and smaller modals still can be rotated.

Google Doesn’t Understand Customer Service

Were the Mayans right? I hadn’t received any new email in Gmail since 2:09 on the afternoon of 12/12/12. Was this the beginning of the end?

I normally have a steady drip of messages into my personal inbox throughout the day, even more so in this holiday period as companies that I have a legitimate relationship with, advise me of their latest offers and free shipping deals. But there it was, on the evening of the 12th — absolute silence. The fact that I as expecting some important messages increased my tension.

Only after some manual refreshing did Gmail inform me that it was having problems retrieving my email, prompting me to dig deeper and eventually eliciting the rather terse message:

SSL Security Error.
Server returned error “SSL error: self signed certificate”

My personal email is hosted by a server that sits in the UK and is managed by my brother. I just use Gmail as a convenient client so that I am not tied to a particular desktop, the spam filtering is first rate without any manual training, and the messages are stored safely on a server. The error message would imply that our server was configured incorrectly and, given that early afternoon in Portland is late evening in the UK, a time when a brother would be making changes to a server, would imply that something had been messed up. I know he doesn’t use Gmail as a client, so he may not be aware of the problems he has caused. But could I raise his attention with texts? No I could not!

Well, Kevan, I hereby apologize for besmirching your good name in my thoughts. You were entirely blameless in this escapade. Through some digging, it appears that Google decided to change their procedures and enforce a strict SSL policy. They would now only connect to a server if it has a valid, signed SSL certificate. Any mail server using a self-signed certificate, which are a common occurrence amongst personally managed mail servers such as ours, would be refused.

As an aside, the error message “Server returned error”, is poorly written because it is not clear as to whose server we are talking about. It is not an error that our mail server is returning a self-signed certificate — that is a legitimate thing to do. The problem is that Google is not allowing such an activity. This is not an SSL error, it is a policy of not accepting certain kinds of configurations. The error message is just lazy engineer speak that fails to convey the correct issue.

Now I don’t disagree with the policy change as it helps to protect from man-in-the-middle attacks, but I do condemn the implementation of the change, and it demonstrates that Google is an engineering company and doesn’t understand customer service.

In any production system, if you are going to introduce a change that a) will disrupt the service, or b) force the customer to perform an action, or c) cause the customer to pay some money, then you need to proactively communicate that change. Google, with this SSL policy enforcement, hit that trifecta and absolutely should have told everyone of the change.

The solution to the problem is for us to purchase an SSL certificate from a reputable authority. No big deal, except that this takes time because our identity has to be verified, except that it costs money, and we have no access to email during the transition. I had to hurriedly configure a desktop email client that I could authorize to overlook a self-signed certificate, but this will be a temporary crutch until we can install a signed certificate, and is something that I shouldn’t have to do. From Google’s perspective that is tempting trouble because I might like the new system and give up on Gmail altogether. Clearly the policy change was not fully thought through.

It would have been trivial for Google to determine all the accounts that fetched email from a remote server and verified which of those servers had a self-signed certificate. It should have then sent those accounts an email with the details, reasons and implications of the upcoming change, along with a timeline for its implementation. In this particular case, because it requires the purchase of an SSL certificate, there should have been at least a week’s notice. You cannot just pull the plug on a service if the solution requires a significant time to implement. It shows a total lack of respect for your customers and their needs.

App Tour : Part 4: Mimicking instance variables for a category

This is the final part of a four part series. The first part discussed dynamically injecting video into a UIWebView and followed on in the second part with how to detect scrolling to the end of the HTML page. The third part covered how to handle full screen video on an iPad.

The previous post in this series discussed how to handle full screen video when that video is launched from a popover on the iPad. A popover is not a standard part of the view hierarchy, so when the video is made full screen the popover remains on top of the video, which of course is not a desired effect. The solution is to recognize when the video enters full screen and hide the popover temporarily and restore it when the video exits.

In my application there are currently two different view controllers that present a popover with the potential to show video. Therefore I wanted to wrap the hide and show effects into a nice neat code unit that I could reuse whenever necessary. In Objective-C that usually means a category. I could encapsulate the new hidePopover: and showPopover: methods in a UIViewController category and import that wherever it was required.

Hiding any old popover is easy, but what I wanted to do was to restore the popover to the exact same state as when it was hidden. As my popover contained a UINavigationController and several UITableViewControllers, I needed to return to a view stack several layers deep. The easiest way to do this is to retain a reference to the popover’s content before dismissing it and using that reference when the popover is recreated.

// Temporarily hide a popover
- (void)hidePopover:(NSNotification *)notification {
self.hiddenPopoverContent = nil;
if (self.hiddenPopoverController) {
// Save some details of the current state
// Holding on to the content view means we can go back to the same state
self.hiddenPopoverContent = [self.hiddenPopoverController contentViewController];
self.hiddenPopoverContentSize = [self.hiddenPopoverController popoverContentSize];
id <UIPopoverControllerDelegate> popoverDelegate = [self.hiddenPopoverController delegate];
self.hiddenPopoverDelegate = popoverDelegate;
[self.hiddenPopoverController dismissPopoverAnimated:YES];
self.hiddenPopoverHidingInProgress = YES;
if (popoverDelegate) {
[popoverDelegate popoverControllerDidDismissPopover:self.hiddenPopoverController];
}
self.hiddenPopoverHidingInProgress = NO;
self.hiddenPopoverController = nil;
}
}
// Show a popover that had previously been hidden
- (void)showPopover:(NSNotification *)notification {
if (self.hiddenPopoverContent) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.hiddenPopoverContent];
if ([popover respondsToSelector:@selector(popoverBackgroundViewClass)]) {
[popover setPopoverBackgroundViewClass:[ZingPopoverBackgroundView class]];
}
CGSize popSize = self.hiddenPopoverContentSize;
[popover setPopoverContentSize:popSize animated:YES];
[popover setDelegate:self.hiddenPopoverDelegate];
if (self.hiddenPopoverPresentingButton) {
[popover presentPopoverFromBarButtonItem:self.hiddenPopoverPresentingButton permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
} else {
[popover presentPopoverFromRect:self.hiddenPopoverPresentingRect inView:[self view] permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
}
self.hiddenPopoverController = popover;
}
}
view raw gistfile1.m hosted with ❤ by GitHub

The hidePopover: grabs the popover’s contentViewController before dismissal so that the content is not released, and when showPopover: recreates the popover, it reuses this content. Because the category retains the contentViewController, it’s state is left untouched and the user is returned to the same place in the navigation hierarchy.

Note that hidePopover: also retains information about the size and placement of the popover. Once we have this information then the category does not have to go back to the hosting view controller and ask it to re-present the popover again. As far as the hosting view controller is concerned. nothing has happened.

But the fly in the ointment here, is that categories cannot define new ivars, only methods. So how do we store new information just for the category, without having to extend the main view controller? If we have to modify the base class then that destroys the drop-in effect of the category.

Well, this is were we can drop down and exploit the Objective-C run-time.

With iOS 4.0, Apple introduced associated objects to the Objective-C run-time, providing a mechanism to link two objects together in an ad-hoc manner, without the need to explicitly declare variables. Effectively an object maintains a dictionary to a set of arbitrary objects.

The two main associated object functions are:

#import 

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, void *key)

The association policy is similar to the standard semantic – OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_RETAIN_NONATOMIC, OBJC_ASSOCIATION_COPY_NONATOMIC, OBJC_ASSOCIATION_RETAIN, OBJC_ASSOCIATION_COPY. Therefore an object can retain a reference to another object.

So let’s see how we can wrap this into a neat package for our category.

First we’ll declare a set of private properties and keys for our associations:

static char const * const HiddenPopoverControllerKey = "HiddenPopoverController";
static char const * const HiddenPopoverContentKey = "HiddenPopoverContent";
static char const * const HiddenPopoverDelegateKey = "HiddenPopoverDelegate";
static char const * const HiddenPopoverPresentingRectKey = "HiddenPopoverPresentingRect";
static char const * const HiddenPopoverPresentingButtonKey = "HiddenPopoverPresentingButton";
static char const * const HiddenPopoverArrowDirectionKey = "HiddenPopoverArrowDirection";
static char const * const HiddenPopoverAnimatedKey = "HiddenPopoverAnimated";
static char const * const HiddenPopoverContentSizeKey = "HiddenPopoverContentSize";
static char const * const HiddenPopoverHidingInProgessKey = "HiddenPopoverHidingInProgress";
@interface UIViewController (RBPopoverPrivate)
@property (nonatomic) UIPopoverController *hiddenPopoverController;
@property (nonatomic) UIViewController *hiddenPopoverContent;
@property (nonatomic) id <UIPopoverControllerDelegate> hiddenPopoverDelegate;
@property (nonatomic) CGRect hiddenPopoverPresentingRect;
@property (nonatomic) UIBarButtonItem *hiddenPopoverPresentingButton;
@property (nonatomic) UIPopoverArrowDirection hiddenPopoverArrowDirection;
@property (nonatomic) BOOL hiddenPopoverAnimated;
@property (nonatomic) CGSize hiddenPopoverContentSize;
@property (nonatomic) BOOL hiddenPopoverHidingInProgress;
@end
view raw gistfile1.m hosted with ❤ by GitHub

The properties allow an easy interface to the data in the other methods in the category, e.g. self.hiddenPopoverController and self.hiddenPopoverContent.

In the implementation, we provide our own accessor functions that fetch and store the data as associated objects, with a retain policy, to the base object.

@implementation UIViewController (RBPopoverPrivate)
- (UIPopoverController *)hiddenPopoverController {
return objc_getAssociatedObject(self, HiddenPopoverControllerKey);
}
- (void)setHiddenPopoverController:(UIPopoverController *)hiddenPopoverController {
objc_setAssociatedObject(self, HiddenPopoverControllerKey, hiddenPopoverController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIViewController *)hiddenPopoverContent {
return objc_getAssociatedObject(self, HiddenPopoverContentKey);
}
- (void)setHiddenPopoverContent:(UIViewController *)hiddenPopoverContent {
objc_setAssociatedObject(self, HiddenPopoverContentKey, hiddenPopoverContent, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// plus many more ...
@end
view raw gistfile1.m hosted with ❤ by GitHub

Note, assigning nil to an associated object will release it and remove the association.

Now we have the basics for our category to handle the presentation of a popover when there is a chance that we might need to temporarily hide it due to full screen video.

Here is the full code for the category.

@implementation UIViewController (RBPopover)
- (void)startTrackingHidePopover {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hidePopover:)
name:NOTIFICATION_POPOVER_HIDE
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(showPopover:)
name:NOTIFICATION_POPOVER_SHOW
object:nil];
}
- (void)stopTrackingHidePopover {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NOTIFICATION_POPOVER_HIDE
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NOTIFICATION_POPOVER_SHOW
object:nil];
}
- (void)presentPopover:(UIPopoverController *)popover fromRect:(CGRect)rect permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections animated:(BOOL)animated {
self.hiddenPopoverController = popover;
self.hiddenPopoverPresentingRect = rect;
self.hiddenPopoverPresentingButton = nil;
self.hiddenPopoverArrowDirection = arrowDirections;
self.hiddenPopoverAnimated = animated;
if ([popover delegate]) {
self.hiddenPopoverDelegate = [popover delegate];
}
[popover presentPopoverFromRect:rect inView:[self view] permittedArrowDirections:arrowDirections animated:animated];
}
- (void)presentPopover:(UIPopoverController *)popover fromBarButtonItem:(UIBarButtonItem *)button permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections animated:(BOOL)animated {
self.hiddenPopoverController = popover;
self.hiddenPopoverPresentingButton = button;
self.hiddenPopoverArrowDirection = arrowDirections;
self.hiddenPopoverAnimated = animated;
[popover presentPopoverFromBarButtonItem:button permittedArrowDirections:arrowDirections animated:animated];
}
- (void)dismissedPopover {
// Don't destroy any data if we are in the middle of the hiding process
if (self.hiddenPopoverHidingInProgress) {
return;
}
self.hiddenPopoverDelegate = nil;
self.hiddenPopoverContent = nil;
self.hiddenPopoverController = nil;
self.hiddenPopoverPresentingButton = nil;
}
#pragma mark - Popover notifications
// Temporarily hide a popover
- (void)hidePopover:(NSNotification *)notification {
self.hiddenPopoverContent = nil;
if (self.hiddenPopoverController) {
// Save some details of the current state
// Holding on to the content view means we can go back to the same state
self.hiddenPopoverContent = [self.hiddenPopoverController contentViewController];
self.hiddenPopoverContentSize = [self.hiddenPopoverController popoverContentSize];
id <UIPopoverControllerDelegate> popoverDelegate = [self.hiddenPopoverController delegate];
self.hiddenPopoverDelegate = popoverDelegate;
[self.hiddenPopoverController dismissPopoverAnimated:YES];
self.hiddenPopoverHidingInProgress = YES;
if (popoverDelegate) {
[popoverDelegate popoverControllerDidDismissPopover:self.hiddenPopoverController];
}
self.hiddenPopoverHidingInProgress = NO;
self.hiddenPopoverController = nil;
}
}
// Show a popover that had previously been hidden
- (void)showPopover:(NSNotification *)notification {
if (self.hiddenPopoverContent) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.hiddenPopoverContent];
if ([popover respondsToSelector:@selector(popoverBackgroundViewClass)]) {
[popover setPopoverBackgroundViewClass:[ZingPopoverBackgroundView class]];
}
CGSize popSize = self.hiddenPopoverContentSize;
[popover setPopoverContentSize:popSize animated:YES];
[popover setDelegate:self.hiddenPopoverDelegate];
if (self.hiddenPopoverPresentingButton) {
[popover presentPopoverFromBarButtonItem:self.hiddenPopoverPresentingButton permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
} else {
[popover presentPopoverFromRect:self.hiddenPopoverPresentingRect inView:[self view] permittedArrowDirections:self.hiddenPopoverArrowDirection animated:self.hiddenPopoverAnimated];
}
self.hiddenPopoverController = popover;
}
}
@end
view raw gistfile1.m hosted with ❤ by GitHub

To use it, a view controller should call startTrackingHidePopover and use either of the presentPopover: replacements to initially show a popover. When the popover is finally dismissed, call dismissedPopover, and stopTrackingHidePopover when you leave the view.

  • Part 1: Dynamically injecting video into a UIWebView
  • Part 2: Detecting scrolling to the end of a UIWebView page
  • Part 3: Handling full screen video

iOS6 and Rotation Whack-a-Mole

Recently I started a new iOS app primarily to research a few user interaction effects, but it may turn into a real-life app. As a universal app the iPad naturally support all rotations, but on the iPhone I wanted both portrait and landscape views despite the large difference in aspect ratio.

Apple introduced a new way of handling rotation in iOS6, which the documentation covers pretty well. In the long term it should make things simpler, but in the short term developers will need to support the iOS5 way by responding to the shouldAutorotateToInterfaceOrientation: method, and the iOS6 way by coordinating the Info.plist rotation settings, the application delegate’s application:supportedInterfaceOrientationsForWindow: and a view controller’s supportedInterfaceOrientations methods. The easiest way to handle the latter is to create a category for UIViewController:

@implementation UIViewController (Rotation)
// iOS 6
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return IPAD ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskAllButUpsideDown;
}
@end
view raw gistfile1.m hosted with ❤ by GitHub

These methods indicate which rotations are valid for all views, and indeed work very nicely when you rotate the device. But, and the whole point of this blog post is built around the but, there are a few issues to overcome with iOS6.

Select Meal TypeIn one area of the app, I wanted to present a small modal dialog on top of the current view to allow the user to select an item from a list. A semi-transparent shade layer appears underneath the modal to trap any taps on the screen (taps on this layer cancels the modal much like a UIPopover) but still allow the user to see what is underneath, and the whole presentation is less glaring transition than the standard full screen change, which hides the current context.

The standard iOS way for a view controller to show a modal view is via presentViewController:animated:completion:. It is relatively easy to insert a semi-transparent view into the presented controller’s view hierarchy, add a gesture recognizer to collect the taps on that background and to show a pretty view on top. But, and here comes that but, when you rotate the device the modal view controller rotates very nicely just as it should, the presenting view controller, the one that is visible underneath your shade doesn’t budge. This because iOS on the iPhone presents modals as full screen and saves some work by not sending rotation events to the view controllers earlier in the stack.

This state of affairs caused much gnashing of teeth – the mismatched text orientation between the two levels looked ugly, but I really wanted the semi-transparent effect to reduce the weight of the user interface. I also wanted to retain the modal in its own view controller because I anticipated using the pattern in several places and I didn’t want to load up the main view controller with a bunch of extra views.

I tried many things, some of them fairly ugly hacks, but in the end there was effectively a single line of code solution.

iOS5 introduced the concept of custom container view controllers. There have always been container view controllers such as UINavigationController and UITabBarController in iOS, but now we can implement our own containers with our own logic for transitioning between views or indeed to present several views concurrently on a screen, each one managed by its own controller.

[self addChildViewController:modalViewController];
[[modalViewController view] setFrame:[[self view] bounds]];
[[self view] addSubview:[modalViewController view]];
[modalViewController didMoveToParentViewController:self];
view raw gistfile1.m hosted with ❤ by GitHub

The addChildViewController: call establishes the relationship between the container and modal, then we say whereabouts the child will appear on the screen so that the child’s view is part of the container’s view hierarchy. Custom containers must call didMoveToParentViewController: explicitly to tell the child that the transition is complete. The child container can override this method to react to its state change. When you are ready to delete the child view there is a removeChildViewContainer: method. Containers will automatically forward rotation events to their child controllers if the shouldAutomaticallyForwardRotationMethods returns YES, which happens to be the default.

Bonus tip: setting the modal’s frame before displaying it is important because it avoids any problems with positioning views during rotation. I didn’t do this in my initial implementation and saw some rather bizarre locations of views after rotation.

So there we are, I have the appearance of a modal laid on top of a view, everything visible rotates perfectly, and the main view and modal views have their own controllers for good code separation.

Design a site like this with WordPress.com
Get started