Search
Follow
Recent Comments
Monday
May142012

Detecting VoiceOver Status Changes

This is a follow up to my recent post about adding VoiceOver Accessibility support to an iOS app to cover situations where you need to adjust the user interface when VoiceOver is active.

Gestures and Accessibility

I generally dislike Apps that rely exclusively on gestures such as touches, swipes or shaking the device to perform an operation. The gestures may well have been obvious to the developer but as a user I often struggle to discover what I am supposed to do. This leads to a situation where you end up randomly touching, pinching and swiping the screen to discover “hidden” features. This not only leads to a poor user experience but also presents real challenges to accessibility as the gestures are generally not available when VoiceOver is active.

I like to think of gestures as being the equivalent of a keyboard shortcut for a desktop application. The keyboard shortcut allows power users to perform an operation more efficiently but a toolbar icon or menu option allows a normal user to discover and perform the same operation. Likewise if an app makes use of a gesture it should generally have some visible on-screen control that can be used to perform the same operation. A good example is the common iOS gesture of swiping to delete a row in a table. If you are not aware of the gesture you can still use the edit button in the toolbar and then use the on-screen controls to delete rows. This also removes any issues with VoiceOver as long as you set the accessibility attributes for the on-screen controls.

Relying on a gesture as the only way to access a feature not only makes your app difficult to use with VoiceOver it also makes your app difficult to use for all users. Having said that if you have a situation where you really do not want to have an on-screen control you should at least consider modifying the user interface of the app when VoiceOver is active.

Another example that can cause accessibility issues can occur when you are displaying some information for a limited period of time. For example you might keep some details about a download on-screen for a few seconds after it completes. Using VoiceOver those few seconds might not be enough time for the user to navigate to, select and have VoiceOver read the label. In both cases we need to be able to detect VoiceOver status and change the behaviour and maybe even the user interface when it is active.

Revisiting the Task Timer

I previously created a simple task management app to show the basic techniques involved in adding VoiceOver support. The full details are contained in this previous post but the basic idea was to allow the user to create tasks and then time how long they took to complete. I am going to add a new feature to the app which will allow us to reset the duration of a task by shaking the device. This is just the sort of bad user interface design that I was referring to earlier but one which I hope will illustrate the idea.

Before we take a look at detecting whether VoiceOver is active we need to go on a small diversion to implement the shake to reset feature. Luckily this turns out to be trivial. When a user shakes a device the acceloremeter generates motion events. The motion events can be received by any subclass of UIResponder but we need to make sure that our view controller can become the first responder:

- (BOOL)canBecomeFirstResponder

{

    return YES;

}

We do not care in which direction the device is shaken so to detect the shake we implement motionEnded:withEvent: in the task view controller:

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event

{

    [self taskResetAction];

}

I will not bother showing the details of the taskResetAction here as it is not relevant to this discussion. You can check the code if you are interested. Note that there is no way to test device shaking in the simulator so you will need to deploy to an actual device if you want to verify that the shake event actually works. The shake action will still work even if VoiceOver is activated but to improve the accessibility of the app we will look at how we can provide an alternative user interface when we detect VoiceOver is in use.

Checking the Status of VoiceOver 

There are two ways to determine or be informed of the status of VoiceOver. The first way is to call the UIKit function UIAccessibilityIsVoiceOverRunning(). This returns a BOOL indicating if VoiceOver is active. The second way is to register for the UIAccessibilityVoiceOverStatusChanged notification. We will use both of these techniques to show a reset button in our detailed task view whenever VoiceOver is active:

The first thing we will do is add the reset button to both the iPhone and iPad Nib files for the task view controller and connect its outlet and action. The reset button is just a UIButton with a custom image inserted into the task view as shown below:

We can set the accessibility label for the button directly in Interface Builder:

To ensure that the button is not normally visible we also set the hidden property in Interface Builder:

Our view controller implements an IBOutlet property that is wired up to the button.

@property (weak, nonatomic) IBOutlet UIButton *taskResetButton; 

The button action is connected to the same taskResetAction we used previously to respond to the shaking motion event:

- (IBAction)taskResetAction;

Now when our view first loads the viewDidLoad method in the view controller is used to configure the view. So we can use a call to UIAccessibilityIsVoiceOverRunning() to see if VoiceOver is already active and if so ensure our reset button is visible:

self.taskResetButton.hidden = ! UIAccessibilityIsVoiceOverRunning();

That takes care of situations where VoiceOver is in use when our view loads but not if it is activated or disabled when our view is already onscreen. For that we need to register an observer for the notification in the viewDidLoad method:

- (void)viewDidLoad

{

    ...
    ...


    [[NSNotificationCenter defaultCenter] addObserver:self 
                           selector:@selector(voiceOverStatusChanged)
                               name:UIAccessibilityVoiceOverStatusChanged
                             object:nil];

}

To be sure we clean up when our view controller goes away we should of course unregister the observer in viewDidUnload and dealloc. Since we are using ARC we have not so far implemented dealloc so we need to add it now just to remove the observer:

- (void)dealloc

{

    [[NSNotificationCenter defaultCenter] removeObserver:self];

}

The notification does not actually provide a parameter indicating the VoiceOver status but that is not an issue as we can simply call UIAccessibilityIsVoiceOverRunning again to get the current status. Depending on the status we can show or hide the reset button:

- (void)voiceOverStatusChanged

{

    self.taskResetButton.hidden = ! UIAccessibilityIsVoiceOverRunning();

}

Now as we enable or disable VoiceOver with the task view visible the reset button is dynamically shown or hidden in response.

Summary

As I discussed at the start of this post if you find yourself needing to modify your app behaviour for VoiceOver it may actually be a warning of a more fundamental problem in your interface design. For those situations where you really do need to do something different I hope this post proves to be useful. You can find the modified Xcode project used in this post in my GitHub CodeExamples repository.

Thursday
May102012

Programming iOS 5 by Matt Neuburg

My list of book resources has lacked a strong recommendation for iOS for some time. There are a lot of good introduction to iOS programming books available but I struggle to recommend just one as the definitive guide. Programming iOS 5, 2nd Edition by Matt Neuburg (O’ Reilly Media, March 2012) is a major update to the author’s earlier Programming iOS 4 book which may just fill the gap.

First Impressions

The first thing I have to say is that this is a massive book with the dead-tree version weighing in at 1016 pages. Luckily I read the ePub version using iBooks which I prefer for technical books. You are going to want to annotate, highlight and bookmark for future reference with this book. You can read it from cover-to-cover but it will also serve you well as a reference book when you need to review topics. Note though that if you are looking for a tutorial or cookbook style approach this is probably not the book for you. I guess Matt may have received some criticism for the iOS 4 version of the book as he makes the purpose of this edition very clear up front:

The purpose of this book is to proceed by focusing on the underlying knowledge needed for an actual understanding of iOS programming. That is precisely the opposite of a cookbook. This book has no simple recipes ready for you to drop into your own code and come up with an app. I don’t give you some fish; I teach you what a fish is and what you need to know to obtain one.

Even though it is not a cookbook all of the code examples are available on GitHub which is an approach I wish all authors would adopt. It makes it so much easier to browse code and also ensures that any minor typos get fixed quickly. Emphasis is given to features new to iOS 5 such as ARC and storyboarding which helps if you are transitioning from iOS 4 but no prior iOS knowledge is assumed.

In Depth

The book is divided into seven major sections as follows:

  • Section I. Language
  • Section II. IDE
  • Section III. Cocoa
  • Section IV. Views
  • Section V. Interface
  • Section VI. Some Frameworks
  • Section VII. Final Topics

The first five sections represent maybe two thirds of the book and step by step provide a thorough coverage of the fundamentals of iOS 5 development. A lot of iOS programming books either assume prior knowledge of C or include a quick summary in an Appendix. This books kicks off with coverage of both the C and Objective-C languages and then introduces Xcode and Nibs.

With the basics out of the way Cocoa is introduced by way of the foundation framework, categories and protocols, notifications, the delegation pattern, actions and the responder chain. It is also at this point that the detailed discussion of memory management is introduced which is a mandatory and key topic for any Cocoa book. The book is written with the assumption that you will be using ARC but also stresses that you still need to understand the Cocoa memory management model behind ARC.

The Views section provides good explanations on the view hierarchy, frames and coordinate systems, drawing, layers, animation, handling touches and gestures. The Interface section completes the fundamentals with coverage of view controllers, table views, popovers and split views and an exhaustive look at the numerous other views and controls.

The last two sections of the book tie up some loose ends with coverage of additional frameworks and topics that don’t fit elsewhere. This includes coverage of many of the more common frameworks including audio, video, music library, photo library, address book, calendar, mail, maps and sensors (e.g. location and acceleration). The final section finishes up with some quick coverage of file management, networking and threads (including both NSOperation and Grand Central Dispatch). The use of iCloud is also covered briefly in the discussion on the UIDocument model.

Likes and Dislikes 

What I like about this book are the numerous hints, tips and opinions from Matt as he covers each topic. I don’t want to see a reproduction of reference material that I can just as easily get from Apple. What I want is insight into what an experienced developer thinks about the topic at hand. For example, there is a very good explanation of storyboards but what I found interesting about that section was Matt’s strong opinion on storyboards and why you might not want to use them. The explanation on popovers gets a similar treatment and in both cases I have to agree with the conclusions that Matt reaches.

There were so many new features introduced with iOS 5 that I think even somebody with iOS 4 experience will find this book useful. Changes from iOS 4 are highlighted throughout the book which should help when making the transition.

Even though this is a huge book there are still some topics that are not covered. It is easy to forget how big the iOS SDK has become so I think it is fine to skip topics like Game Kit, iAD integration, Printing, In App Purchase and the long list of other iOS Frameworks and topics. If you have a good understanding of the basics you should have no problem figuring out the less common frameworks on your own.

Personally I would have liked to see Core Data and maybe also Accessibility covered. I consider Core Data to be a fundamental Cocoa technology and as I posted recently I would like to see Accessibility get some more attention in general from developers and book authors. Section VI of the book covers a number of interesting but not vital frameworks such as accessing the music and photo libraries, the address book and calendar which I would exchange for some coverage of Core Data. That is a very minor criticism though for what is otherwise a comprehensive coverage of the iOS universe. 

Final Comments

There is a lot to recommend about this book and I don’t just mean because of the size. The primary purpose of the book is to provide you with a deep understanding of the language and iOS frameworks and I think it succeeds in that task. Of course that takes some effort on the part of the reader and I can imagine that this book will not suit everybody. Clearly if you are looking for quick high level survey of iOS 5 with some sample code to get you started this is not the book for you. On the other hand if you are prepared to invest the time I would be surprised if even experienced iOS developers can read this book without learning something.

Monday
May072012

Static Table Views with Storyboards

When I first heard that iOS 5 had introduced the concept of a static table view that could be designed in Interface Builder I was disappointed to see that it was actually tied into storyboards. I have nothing against storyboards, they are an interesting innovation that can (potentially) save a lot of code. However if you are not yet ready to fully adopt storyboards for your user interface design you may be discouraged from exploring the new features they bring. What I want to examine in this post is how you can use a minimal implementation of storyboards to quickly create a static table view without having to completely rewrite an existing App.

Designing A Settings Table

Implementing a Settings table is an obvious use for static table views within an App. To illustrate how easy this is I am going to start from the Xcode template for a tabbed application. Note that I did not select the option to use storyboards so that the main interface is still created using the traditional Nib approach. (I am however using ARC so pay attention if you are cut and pasting code into an App not using ARC). The template gives us a tab bar controller containing two view controllers both loaded from individual Nib files. The second view controller is what we will replace with a storyboard to implement our application settings views.

To get started I am going to create a storyboard and layout a number of table views. To keep this post short I am going to limit this to an iPhone layout but the principle applies equally for an iPad or Universal App. To add a storyboard create a new file and select the storyboard from the User Interface templates, ensure the device family is set to iPhone and name the file “Settings”:

The first table view controller that I am going to add to the storyboard is going to represent a top level settings menu allowing me to group simple and advanced settings which I will access on separate views. When I have finished the table view should look like this:

By default when you drag a table view controller into a storyboard you get a dynamic table view. You can change this by selecting “Static Cells” in the Attributes Inspector:

What we now have is a table view containing three empty cells that we can use to design our top level settings menu:

To get to our desired layout the main steps are as follows:

  • change the table view style from “Plain” to “Grouped” and delete two of the rows to leave a single row (you can change the number of rows using the Inspector but to delete a row it is quicker to just click on the rows in the table and hit the delete key)
  • drag a label object into the table view cell, adjust the formatting as you see fit and set the text label to “General”
  • with the Table View Section selected in either the document navigator or in the jump bar use the Attributes Inspector to set the section header and footer (this is also where you would adjust the number of rows in the section):

To add the second section ensure the Table View is selected in the document navigator and then change the number of sections in the Attributes Inspector. When the new section is added it is copied from the first section which is a nice time saver. To complete the layout we just need to modify the header, footer and label text.

Now we have our initial settings table view we can repeat the exercise to add our detailed settings tables but before we do that we need to embed our table view controller in a navigation controller to handle moving between the various tables. That is an easy step using the Embed In > Navigation Controller option from the Xcode Editor menu. Once the Navigation Controller has been added we can also set the title in the Navigation Bar for our Settings table view controller. At this point the Storyboard should look as follows:

Now we can repeat the exercise for two additional table view controllers which will provide static table views for our simple and advanced settings. I will not go through this step by step but at the end of the process the Storyboard will look something like this:

At this point we need to add the transitions from the Settings table view controller to each of the detailed settings view controllers. The Storyboard editor refers to each of the views as scenes and transitions between scenes as segues. We need two segues in this case, the first from the “General” table view cell to take us to the General Settings table view and the second from the “Extra Settings” table view cell to the Advanced Settings table view. To create the segues you need to control click on the source table view cell and then drag across to the destination table view. When you release the mouse button you are presented with a popover menu allowing you to select the Storyboard segue style. Since we are using a navigation controller we will use the push segue.

With our new view controllers now included in the navigation hierarchy we can set the title for each view by typing directly in the navigation bar of each view so that our Storyboard now looks as follows (Interface Builder will infer when it needs to add the navigation and tab bars to views):

Loading the storyboard

If we were using a storyboard for the main user interface we could specify the filename in the UIMainStoryboardFile key in the application Info.plist file and it would be loaded automatically. Since in this example we want to make a more limited use of storyboards we need to manually load and launch our storyboard file. We can do that by modifying our Application Delegate to use the storyboard when loading the view controller for the second tab bar item. The relevant code in the application:didFinishLaunchingWithOptions method is as follows:

UYLFirstViewController *firstViewController = [[UYLFirstViewControlleralloc] initWithNibName:@”UYLFirstViewController”

 bundle:nil];

 

UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:@”Settings” bundle:nil];

UIViewController *settingsViewConroller = [settingsStoryboard instantiateInitialViewController];

 

settingsViewConroller.title = NSLocalizedString(@”Settings”, @”Settings”);

settingsViewConroller.tabBarItem.image = [UIImage imageNamed:@”second”];

 

self.tabBarController = [[UITabBarControlleralloc] init];

self.tabBarController.viewControllers = [NSArray arrayWithObjects:firstViewController, 

                                                           settingsViewConroller, nil];

self.window.rootViewController = self.tabBarController;


Lines 2 and 3 are the important ones, the UIStoryboard class method storyBoardWithName:bundle: is used to load the Storyboard file. There is no need to specify the bundle when the storyboard is contained in the main application bundle. The instantiateInitialViewController method then allocates and initialises our root navigation view controller which we can then add to the tab bar view controller along with the first view controller which was created from its Nib file.

Now if we run the App in the Simulator a lot the Settings table view is already working including pushing and popping the detailed view controllers on and off the navigation controller stack. So far this looks like a real win for storyboards in that we have a lot of functionality designed in Interface Builder without having to write any boilerplate view controller code. Since our initial settings table view controller only acts as a top level menu it appears that we do not even need to implement a custom subclass for it (I say “appears” as we will see shortly that there is one issue we need to deal with that causes us to create a generic table view subclass).

Implementing Row Selection

To actually make our settings screens useful we need to implement the view controllers so that we can interact with the user interface and read and write the settings. The general settings screen (shown below) is the easiest so we will start by adding a subclass of UITableViewController named UYLGeneralSettingsTableViewController.

The public interface for this class is empty:

@interface UYLGeneralSettingsTableViewController : UITableViewController

 

@end

 

We will add two properties to the private class extension to represent to two settings (speed and volume) that this view manipulates:

@interface UYLGeneralSettingsTableViewController ()

 

@property (nonatomic) NSUInteger speed;

@property (nonatomic) NSUInteger volume;

 

@end

 

Now we need to implement just enough of a table view controller to allow us to setup the view state when it first loads and to allow us to change the selected row in each of the two sections. Before I show the code that I added it is important to note the code that I do NOT need to implement:

  • the UITableViewDataSource protocol has two required methods and a number of optional methods that are now largely redundant. Of the two normally required methods I will in this case implement tableView:cellForRowAtIndexPath: (to set the checkmark of the selected row) but it may not always be required.
  • There is no need to implement the other required method tableView:numberOfRowsInSection: or numberOfSectionsInTableView: as the table dimensions of the static table were set in InterfaceBuilder.
  • We do not need to implement tableView:titleForHeaderInSection: or tableView:titleForFooterInSection: as we directly set the header and footer in the Storyboard.

To initialise the view we will implement the viewDidLoad method to read our user defaults from file.

- (void)viewDidLoad

{

    [superviewDidLoad];

 

    NSUserDefaults *defaults = [NSUserDefaultsstandardUserDefaults];
    self.speed = [defaults integerForKey:kUYLSettingsSpeedKey];
    self.volume = [defaults integerForKey:kUYLSettingsVolumeKey];

}

 

Now to actually set the checkmark on the currently selected row we will implement the UITableViewDataSource protocol method tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView 

         cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType = UITableViewCellAccessoryNone;

 

    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];

 

    switch (section)
    {
        case SECTION_SPEED:
            if (row == self.speed)
            {
                cell.accessoryType = UITableViewCellAccessoryCheckmark;
            }
            break;

 

        case SECTION_VOLUME:
            if (row == self.volume)
            {
                cell.accessoryType = UITableViewCellAccessoryCheckmark;
            }
            break;
    }
    return cell;

}

Usually the first thing you do in tableView:cellForRowAtIndexPath is attempt to dequeue a cell for reuse of if not available allocate and initialise a new UITableViewCell. However that does not apply in this case as this is a static table view. All of the table view cells that we need were created in Interface Builder and are instantiated when the storyboard loads the view controller. By the way this is worth considering if you are attempting to use static table views to create a huge table view. Having to allocate all of the static table view cells when the view is first loaded may be both slow and cause you memory issues. If you find yourself creating a static table view with more than one or two screens of information you probably want to go back to using a dynamic table view.

Even though we do not need to allocate the table view cells for a static table view we can still get a reference to each cell to allow us to customise the cell. There are several ways to do this including declaring an outlet for each table view cell in Interface Builder which we will see in the advanced settings view controller. In this case though we simply need to set the cell accessory type so we can call tableView:cellForRowAtIndexPath on the super class to return us the current cell. Once we have the current cell we can set or clear the accessoryType based on the current value of the speed or volume properties.

We also need to implement the UITableViewDelegate method to handle row selection (see below). There is nothing remarkable about this method so I will not go into too much details. The method basically just updates the user defaults based on which row is selected and then requests the table view is reloaded to cause the checkmarks to be set.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];

 

    switch (section)
    {
        case SECTION_SPEED:
            self.speed = row;
            [defaults setInteger:row forKey:kUYLSettingsSpeedKey];
            break;

 

        case SECTION_VOLUME:
            self.volume = row;
            [defaults setInteger:row forKey:kUYLSettingsVolumeKey];
            break;
    }

 

    [self.tableView reloadData];

}

Finally we need to go back to Interface Builder and with the Identity inspector change the class of the generic UITableViewController to our custom subclass UYLGeneralSettingsTableViewController:

If you rerun the App in the Simulator the general settings screen should now be fully functional.

Connecting to Static Table View Cells

The advanced settings screen at first looks a little more complicated as we have a number of switches and stepper controls that we need to interact with. In fact whilst it takes a little bit of effort in Interface Builder wiring everything up it actually requires even less code. As before we will start by adding a subclass of UITableViewController to the project and name it UYLAdvancedSettingsViewController:

@interface UYLAdvancedSettingsViewController : UITableViewController

 

@end

 

To be able to interact with the contents of the static table view cells we need to define some outlets to allow us to set the various elements and action methods for the switch and stepper controls. A good place to do that is in a private class extension of our custom subclass:

@interface UYLAdvancedSettingsViewController ()

 

- (IBAction)switchToggled:(UISwitch *)sender;

- (IBAction)stepperChanged:(UIStepper *)sender;

 

@property (nonatomic, weak) IBOutlet UISwitch *warpSwitch;

@property (nonatomic, weak) IBOutlet UISwitch *shieldsSwitch;

@property (nonatomic, weak) IBOutlet UILabel *creditsLabel;

@property (nonatomic, weak) IBOutlet UILabel *retriesLabel;

@property (nonatomic, weak) IBOutlet UIStepper *creditsStepper;

@property (nonatomic, weak) IBOutlet UIStepper *retriesStepper;

 

@end

 

This project is compiled using ARC so we follow Apple recommendations and make all of our IBOutlet properties weak references. Now we use Interface Builder to connect the outlets to the corresponding label or control in the static table view. To do that we first need to use the Identity inspector to change the class of the view from the generic UITableViewController to our custom UYLAdvancedSettingsViewController. I will not walk you through wiring up every user interface element as it is standard Interface Builder stuff but just for reference the view hierarchy and connections are reproduced below:

In order to determine which switch or stepper is triggering the action each one is uniquely tagged within Interface builder.

Now we have our outlets wired up it is trivial in viewDidLoad to set the initial state of the switches, steppers and labels:

- (void)viewDidLoad

{

    [superviewDidLoad];

 

    NSUserDefaults *defaults = [NSUserDefaultsstandardUserDefaults];

 

    self.warpSwitch.on = [defaults boolForKey:kUYLSettingsWarpDriveKey];

    self.shieldsSwitch.on = [defaults boolForKey:kUYLSettingsShieldsKey];

 

    self.creditsStepper.value = [defaults doubleForKey:kUYLSettingsCreditsKey];

    self.creditsLabel.text = [NSStringstringWithFormat:@”%1.0f”, self.creditsStepper.value];

 

    self.retriesStepper.value = [defaults doubleForKey:kUYLSettingsRetriesKey];

    self.retriesLabel.text = [NSStringstringWithFormat:@”%1.0f”, self.retriesStepper.value];

}

Note that we do not need to implement tableView:cellForRowAtIndexPath: to get a cell reference as we have direct access to each cell via the outlets. In fact we do not need to implement any UITableViewDataSource or UITableViewDelegate method. The two action methods to handle the switch and stepper actions are fairly similar so I will just show the method for the switch here:

- (IBAction)switchToggled:(UISwitch *)sender

{

    NSUserDefaults *defaults = [NSUserDefaultsstandardUserDefaults];

 

    switch (sender.tag)
    {
        case TAG_WARPSWITCH:
            [defaults setBool:sender.on forKey:kUYLSettingsWarpDriveKey];
            break;

 

        case TAG_SHIELDSSWITCH:
            [defaults setBool:sender.on forKey:kUYLSettingsShieldsKey];
            break;
    }

}

Handling Orientation Changes

There is one small issue I need to take care of before finishing. As currently implemented there is a problem with the way that we respond to device rotation. In fact if you build and run the App you will see that it does not respond to device orientation changes. The general rule for a tab bar controller is that all of the view controllers in each tab need to allow rotation or nothing rotates. I have not shown it but each of the view controllers we have implemented, including the view controller used for the first tab bar item, implements shouldAutorotateToInterfaceOrientation: to return YES for all orientations:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

    return YES;

}

Unfortunately there is one view controller, the top level settings table view controller which messes things up for us. Since we did not need to interact with the table we never actually implemented our own custom subclass for this table view controller. That means we cannot override the orientation method so we get the default behaviour:

By default, this method returns YES for the UIInterfaceOrientationPortrait orientation only. If your view controller supports additional orientations, override this method and return YES for all orientations it supports.

To resolve this issue I have added a new UITableViewController subclass named UYLRotatingTableViewController that does nothing else but implement the shouldAutorotateToInterfaceOrientation method. Using the Identity inspector to change the class of the settings table view controller to this new subclass fixes the problem. The final storyboard is as follows:

Wrapping Up

One of the criticisms I have heard about storyboards is that unless your App is very simple they do not save you very much code. This is because you still need to handle the flow of data between view controllers. Typically this involves implementing prepareForSeque:Sender: to pass data into a new view controller and implementing a delegate protocol to pass data back to a parent or presenting view controller.

In this example we are making a very limited use of storyboards and there is no real data flow between the different static table view controllers. This means there is no need to implement prepareForSegue:sender: or to create view controller delegates so there is a considerable reduction in the amount of code required. You can make your own mind up on the value of storyboards and when they might be appropriate. Personally I like the ability to add them selectively to an App for specific parts of the interface and static table views are definitely one area where I will use them.

As always you can download the example Xcode project that accompanies this post here or in my Github CodeExamples repository.

Friday
Apr272012

Xcode Balancing Brackets For Method Calls

This may well be my favourite and perhaps also the simplest Xcode completion trick I have come across. I may be the last person to discover it but just in case there is somebody else who is yet to find it…

I always find it a pain to think about how many opening brackets (“[“) I need when starting to write Objective-C statements where you have several nested method calls. This means I often get to the end of the statement and find I have an extra opening bracket somewhere. Luckily Xcode will allow you to forget about the opening brackets and just type the closing brackets. The corresponding opening bracket is inserted for you by Xcode automatically. So for example assume I start typing the following sequence:

NSNumber *number10 = NSNumber alloc

At this point (with the cursor at the end of the line) typing a single closing bracket “]” causes Xcode to insert the corresponding opening bracket so you end up with this:

NSNumber *number10 = [NSNumber alloc]

If I then continue to type the rest of the statement I can repeat the same trick at this point:

NSNumber *number10 = [NSNumber alloc] initWithInteger:10 

Typing the final closing “]” again causes Xcode to insert the corresponding initial “[” to give us the following:

NSNumber *number10 = [[NSNumber alloc] initWithInteger:10]

Adding the final semicolon completes the statement:

NSNumber *number10 = [[NSNumber alloc] initWithInteger:10];

If for some strange reason you do not like this code completion you can disable it in Xcode preferences (look under Code completion in the Text Editing preferences pane).

Monday
Apr232012

VoiceOver Accessibility

Apple has made it really easy to add VoiceOver support to iOS Apps. In many cases setting the accessibility label for each interface element in Interface Builder gets 90% of the job done. What I want to cover in this post is the remaining 10% that will ensure an App interacts well with VoiceOver. If you have never considered making your App accessible you should read this great post by Matt Gemmell on Accessibility for iPhone and iPad apps.

What is VoiceOver?

VoiceOver is a technology built into all iOS (and Mac) devices that provides audible aids to the user when navigating and interacting with an App. The UIAccessibility API was introduced with version 3.0 of the SDK to allow the app to provide additional information to VoiceOver. This includes such things as setting the label and optionally a hint that is read by VoiceOver when an element in the user interface is selected. In addition you set accessibility traits for each UI element indicating for example that it acts like a button or whether or not it has been selected.

The easiest way to understand VoiceOver is to try it on some existing apps and get a feel for how it works. Before diving into an example app I will quickly review how to access VoiceOver and some other tools that you will want to use when testing your App.

Toggling VoiceOver Support on a Device

You can turn VoiceOver support on for a device from the Settings application (Settings > General > Accessibility > VoiceOver). However for testing purposes I find it more convenient to be able to quickly switch VoiceOver on and off from within the App. To do that I set the Triple-click Home button option to Toggle VoiceOver (from the Settings > General > Accessibility screen):

With this option enabled you can easily turn VoiceOver on and off at any point by triple-clicking the Home button on the device. This makes it easy to examine specific situations in your App with and without VoiceOver activated. It can also be an education to enable VoiceOver on other Apps to see how well or badly other developers are handling accessibility.

Accessibility Inspector in the iOS Simulator.

The iOS Simulator does not directly support VoiceOver so you will always need to test on a device to see how well your App performs. However the iOS Simulator does have an extremely useful tool for debugging UIAccessibility issues. It is called the Accessibility Inspector and it can be activated on the Simulator from Settings > General > Accessibility. Once enabled you should see the inspector window displayed:

The Accessibility Inspector does not speak to you but it does provide you with information about the currently selected accessible element. The Inspector can be temporarily disabled/enabled by clicking the close control in the upper left corner of the Inspector. This can be useful when you need to navigate around the app as you can selectively enable the inspector to examine each element in your user interface.

TaskTimer - An Example in Adding Accessibility

The rest of this post is going to be a case study in adding VoiceOver support to an example App called TaskTimer. The app is based on the Xcode Master Detail template and is a variation on a task list manager - the variation being that you can time how long a particular task takes. This is a universal app so both iPhone and iPad User Interfaces are supported. The master view is a UITableView containing the list of tasks as shown in the iPhone screenshot below:

The task list follows the usual conventions of a table view in that new tasks can be inserted using the “+” button in the navigation bar and deleted via the “Edit” button. The actual task data is stored in a core data model. A completed task is shown by the green tick and the actual duration (mm:ss) is shown under the task name in the table cell view. The App is somewhat limited in that it only handles task durations up to 59 minutes 59 seconds long but it is good enough for the purposes of this post. Selecting a row in the table shows the detail view for the selected task which allows the task name to be edited. In addition a custom view allows the task timer to be stopped and started via two buttons with the current task duration displayed in a central text label:

As I mentioned this is a Universal app so just for completeness the iPad user interface is shown below in portrait orientation:

UITableView Accessibility

If we use the Accessibility Inspector in the iOS Simulator we can see how well this App performs before we make any modifications. If we first take a look at the Task List table view we can find some UI elements that work fine and others that definitely need some work. The standard Navigation bar elements such as the Edit button, the title and the Add button all work fine without any effort on our part:

Note how the accessibility traits indicate that the first UI element is a button where as the second element is static text. Correctly setting the accessibility traits of an element is key to ensuring that VoiceOver interacts correctly with the App. Standard iOS UIKit elements such as the Navigation Bar are generally already properly configured. Simple UI elements can also be configured in Interface Builder when creating the interface.

Things are not so good if we look at one of the rows in the table for a completed task:

By default the table view cell has combined the main text label and the detailed text label for the accessibility label. The two fields are separated by a comma which means VoiceOver will pause briefly when reading the label. This is fine but VoiceOver does not understand that 00:10 actually means 0 minutes, 10 seconds - it simply reads “zero - ten” which is not very informative. Note also that there is no mention of the status of the task. In this case the green tick indicates that the task has been completed but the VoiceOver interface is unable to present that piece of information to the user.

So to improve the accessibility of this view we should in the first instance address the following issues:

  • Ensure that when VoiceOver reads the task details it correctly interprets the duration making it clear that it is a time.
  • We should also ensure that the task completion status is indicated for each row in the list

One thing we could also do is add a hint to the table view cell indicating that selecting a row will take the user to the detail task view. In this case I consider that unnecessary as I think it is OK to assume that users understand the basic iOS user interface idioms.

To customise the response from VoiceOver for a UITableView cell we simply need to set the accessibilityLabel property when we configure the cell in the table view controller. In the situation where the task is not yet completed and so does not have a final duration we will stick with the default provided by the cell text label which is the name of the task (task.note). However when the task is complete (task.complete) we want to both indicate the completion status and read the duration in a human friendly way. The following code added to the configureCell:atIndexPath method in UYLTaskListViewController should do the job:

cell.accessibilityLabel = task.note;

 

if ([task.complete boolValue])

{

  NSUInteger minutes = [task.duration integerValue] / 60;
  NSUInteger seconds = [task.duration integerValue] % 60;

 

  cell.detailTextLabel.text = [NSString stringWithFormat:@"%02u:%02u", minutes, seconds];
  cell.imageView.image = [UIImage imageNamed:@"checked.png"];

 

  NSString *durationText = [NSString stringWithFormat:@"%@ %@",task.note,
  NSLocalizedString(@"completed in", nil)];
  durationText = [durationText stringByAppendingString:[task.duration stringValueAsTime]];
  cell.accessibilityLabel = durationText;

}

To generate the text to represent the duration I have created a category (UYLTimeFormatter) on the NSNumber class to add the method stringValueAsTime since we will need it more than once. An example of the output using the Accessibility Inspector is shown below:

Custom View Accessibility

So far adding VoiceOver support has been very easy since it has just involved setting the accessibilityLabel for certain key UI elements. However for the detailed task view things get slightly more complicated. A custom class UYLCounterView is used to implement the numeric counter and the two buttons used to start and stop the counter. By default the buttons will use either the title if set or the name of the UIImage file to provide an accessibility label.  In this case I am using images named start.png and stop.png which provides a reasonable default but note that these labels are of course not localised.

A more serious problem though is that the value of the counter is not accessible at all. The problem is that the text is drawn in the view using a drawAtPoint method so there is no way to directly set an accessibilityLabel or other accessibility properties for the counter. The solution is to implement the UIAccessibilityContainer Protocol for the custom view. This allows us to define arbitrary rectangles in the view that represent the elements we want to make accessible. In this case the view contains three elements, the start button, the stop button and the counter that we want to make accessible.

The UIAccessibilityContainer protocol is an informal protocol so we do not need to declare anything in our UYLCounterView class indicating that we are adopting it. What we will need though is a mutable array to hold our accessible elements. The following property is added to the UYLCounterView interface definition:

@property (nonatomic, strong) NSMutableArray *accessibleElements;

We need to create and configure a UIAccessibilityElement object for each of the three elements in our custom view and store them in this array. To avoid unnecessary effort when VoiceOver is not active we will lazily create them in the getter method in the UYLCounterView implementation. The first thing we do therefore in the getter is to check if we have already created the array and if not we allocate it:

- (NSArray *)accessibleElements

{

  if (_accessibleElements != nil)
  {
      return_accessibleElements;
  }

 

  _accessibleElements = [[NSMutableArrayalloc] init];
Now we need to create a UIAccessibilityElement and correctly set the accessibility properties for each view element. We need to add the elements to the accessibleElements array in the order they are presented to the user from top-left to bottom-right. So the first element we will create is for the start button:

  UIAccessibilityElement *startElement = [[UIAccessibilityElementalloc] 
initWithAccessibilityContainer
:self];
  startElement.accessibilityFrame = [self convertRect:self.startButton.frame toView:nil];
  startElement.accessibilityLabel = NSLocalizedString(@"Start", nil);
  startElement.accessibilityTraits = UIAccessibilityTraitButton;
  if (self.startButton.enabled == NO) 
startElement.
accessibilityTraits |= UIAccessibilityTraitNotEnabled;

 

  [_accessibleElements addObject:startElement];

Some notes about each of the above lines of code

  1. A UIAccessibilityElement is created using the initWithAccessibilityContainer instance method. This method takes a single parameter which is the containing view. In this case the UYLCounterView is the containing view so we can simply use self.

  2. The accessibilityFrame property is where we specify the area of the view that we want to use for this accessibility element. For the start button this is just the frame of the UIButton but note that the accessibility frame must be specified in screen coordinates. This is an important point to understand, if you use the view coordinate system you will get unexpected results especially when the view rotates (more on handling view rotation later). The easiest way to convert the button frame to the screen coordinates is to use the UIView instance method convertRect:toView: specifying nil as the target view, this will give us a frame in the screen coordinate system.

  3. Once we have an accessibility element allocated we can set the accessibilityLabel as with standard UIKit controls. We can, if required, also set an accessibilityHint but in this case we will just set the label to “Start”.

  4. The accessibilityTraits property is this case indicates that this element behaves as a button

  5. We need to provide VoiceOver with a indication when the button is enabled/disabled. Usually a UIButton will take care of this for us but since we are creating our own accessibility element to represent the button we need to add the UIAccessibilityTraitNotEnabled trait when the button is not enabled.

  6. Finally we add the element to our array of accessibileElements.

The next element represents the text drawn in the centre of the view for the timer counter. Calculating the frame for the text is complicated by the need to convert between the view and screen coordinate systems. The code to create the accessibilityElement for the counter is as follows:

  CGRect frame = [self convertRect:self.accessibilityFramefromView:nil];

 

  UIAccessibilityElement *counterElement = [[UIAccessibilityElementalloc] 
initWithAccessibilityContainer:self];
  CGRect textFrame = CGRectInset(frame, UYLCOUNTERVIEW_MARGIN + 
self.startButton.bounds.size.width +
UYLCOUNTERVIEW_MARGIN,
UYLCOUNTERVIEW_MARGIN);
  counterElement.accessibilityFrame = [self convertRect:textFrame toView:nil];
  counterElement.accessibilityLabel = NSLocalizedString(@"Duration", nil);
  counterElement.accessibilityValue = [[NSNumber numberWithInteger:self.secondsCounter] 
stringValueAsTime];
  counterElement.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;

 

  [_accessibleElements addObject:counterElement];

Note that in this case we start by converting the accessibilityFrame of the view to our local view coordinate system from the screen coordinate system. We can then calculate the frame for the text using an inset for the margin and size of the buttons. Finally before setting the accessibilityFrame for the element we need to convert back again to screen coordinates.

As well as setting the accessibilityLabel for this element we also set an accessibilityValue string to represent the current value of the counter. We will see how this gets updated later but note that for elements that can have a value which changes (such as volume control) it is better to use the accessibilityLabel to describe the function of the control and accessibilityValue to represent the current value. In this case the label is set to “Duration” and the value will the actual value of the counter (for example “2 minutes 10 seconds”).

Since the value of the counter can update frequently (in this case once a second) we set the UIAccessibilityTraitUpdatesFrequently trait. We will discuss the impact of this attribute and some other options once we have seen the rest of the setup code.

Finally we add the element for the stop button and return the completed array. Since the stop button code is very similar to the code for the start button I will include the code without further comment:

  UIAccessibilityElement *stopElement = [[UIAccessibilityElementalloc] 
initWithAccessibilityContainer:self];
  stopElement.accessibilityFrame = [self convertRect:self.stopButton.frame toView:nil];
  stopElement.accessibilityLabel = NSLocalizedString(@"Stop", nil);
  stopElement.accessibilityTraits = UIAccessibilityTraitButton;
  if (self.stopButton.enabled == NO) 
stopElement.
accessibilityTraits |= UIAccessibilityTraitNotEnabled;

 

  [_accessibleElements addObject:stopElement];

 

  return_accessibleElements;

}

With the accessibileElements defined we need to implement three very simple access methods required by the UIAccessibilityContainer protocol. These methods provide an interface to our accessibileElements array:

- (NSInteger)accessibilityElementCount

{

  return [[self accessibleElements] count];

}

 

- (id)accessibilityElementAtIndex:(NSInteger)index

{

    return [[self accessibleElements] objectAtIndex:index];

}

 

- (NSInteger)indexOfAccessibilityElement:(id)element

{

    return [[self accessibleElements] indexOfObject:element];

}

To ensure the UIAccessibilityContainer protocol and our newly defined accessibileElements take effect we need to ensure that the UYLCounterView does not itself respond to accessibility requests. To do that we need to implement the isAccessibilityElement for the custom view and ensure it returns NO:

- (BOOL)isAccessibilityElement

{

  return NO;

}

We still have a few refinements to make but we already have enough of an implementation to make our custom view accessible. The Accessibility Inspector now gives us a result when we select the counter text as follows:

Updating the Counter Value

When we created the UIAccessibilityElement for the counter text we set the accessibilityValue property to the current value of the counter. Since the counter is updated every second when the counter is running we need to ensure that we also update the accessibilityValue. We can easily do that in the setter for the secondsCounter value maintained by the UYLCounterView:

- (void)setSecondsCounter:(NSUInteger)secondsCounter

{

  if (secondsCounter > UYLCOUNTERVIEW_LIMIT)
  {
      secondsCounter = UYLCOUNTERVIEW_LIMIT;
  }

 

  _secondsCounter = secondsCounter;

 

  if (_accessibleElements)
  {
      UIAccessibilityElement *counterElement = [self.accessibleElements 
objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_COUNTERTEXT];
      counterElement.accessibilityValue = [[NSNumber numberWithInteger:secondsCounter] 
stringValueAsTime];
  }

 

  [selfsetNeedsDisplay];

}

Note that that accessibleElements array should only be allocated via the getter method we saw previously when VoiceOver is active. We therefore use the ivar to check if it has been allocated before attempting to access it. After retrieving the UIAccessibilityElement for the counter we then set the accessibilityValue using our NSNumber stringValueAsTime category method.

Setting UIButton Traits

The next refinement we need to make is to update the accessibility traits for the two UIButton controls. When the start button is used it disables itself and enables the stop button. We can update the accessibilityTraits of the corresponding UIAccessibilityElement in the method that is triggered by the UIButton action to indicate to VoiceOver which buttons are enabled:

- (void)startAction:(UIButton *)sender

{

  ...
  ...


  if (_accessibleElements)
  {
      UIAccessibilityElement *startElement = [self.accessibleElements
objectAtIndex
:UYLCOUNTERVIEW_ELEMENTINDEX_STARTBUTTON];
      startElement.accessibilityTraits = UIAccessibilityTraitButton | 
UIAccessibilityTraitNotEnabled;

 

      UIAccessibilityElement *stopElement = [self.accessibleElements
objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_STOPBUTTON];
      stopElement.accessibilityTraits = UIAccessibilityTraitButton;        
  }

 

  ...
  ...

}

Likewise when the stop button is used it disables itself so we also need to set the accessibility trait to indicate the new button state:

- (void)stopAction:(UIButton *)sender

{

  ...

 

  if (_accessibleElements)
  {
      UIAccessibilityElement *stopElement = [self.accessibleElements
objectAtIndex
:UYLCOUNTERVIEW_ELEMENTINDEX_STOPBUTTON];
      stopElement.accessibilityTraits = UIAccessibilityTraitButton | 
UIAccessibilityTraitNotEnabled;
  }

 

  ...

}

Handling View Rotation

I am not sure why but most accessibility example Apps do not support device orientation changes. I can only suspect that this is due to the extra complexity involved in dealing with the conversion between screen and view coordinate systems. The implementation of the accessibileElements getter in our UYLCounterView takes this conversion into account when calculating the accessibilityFrame for each element. This ensures that the frame is set correctly based on the device orientation at the time the getter is invoked. There is however a problem if the orientation changes once we have calculated the frame. Since we never recalculate these frames they will no longer be correct if the orientation changes. To illustrate the problem this is how the accessibilityFrame for the counter text appears if we rotate from portrait to landscape:

To fix this problem we need to force the accessibility frames to be recalculated when the device orientation changes. The easiest way I have found to do that is to detect the orientation change in the view controller and force the accessibleElements array to be recreated. To detect the orientation change in the UYLTaskViewController we can implement the didRotateFromInterfaceOrientation method:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

{

  self.taskCounterView.accessibleElements = nil;

}

Now the next time that the accessibleElements array is accessed it will be created from scratch and the frames that are created will be based on the new orientation. We can check that with the inspector with the simulator in landscape:

Notifications

When I covered the creation of the UIAccessibilityElement for the text counter I mentioned that we were using the UIAccessibilityTraitUpdatesFrequently trait but I did not fully describe the effect that this achieves. To fully understand how VoiceOver works in this case you will need to build the example App and install it on a physical device. With the text element selected VoiceOver announces the changing timer value every few seconds. In this case that turns out to be a good choice as the value is changing too quickly for VoiceOver to keep up. In my testing I found that VoiceOver would announce a new value every 4-5 seconds. Note that a new announcement is only made when the value is actually changing so if the time is stopped the announcements also stop. If you take a look at how the StopWatch function within the Apple Clock App works you will find that it also uses a similar technique.

However if we wanted to force VoiceOver to announce every change to the counter view we can tell it that something has changed by posting a notification. The notifications used by VoiceOver are a little different from the usual iOS notifications. To create a notification you need to use a UIKit function named UIAccessibilityPostNotification. This function takes two parameters, the first to specify the notification type and the second an optional notification specific parameter which is usually nil.

To indicate that something on the screen has changed we have two choices for the notification type. The first possibility is to send a UIAccessibilityLayoutChangedNotification to indicate that one or more, but not all, elements on the screen have changed. The other possibility is to send a UIAccessibilityScreenChangedNotification to indicate that the whole screen has changed and VoiceOver should reset. In this case we are only changing the duration text so we could send the layout changed notification by inserting the following function call into the setter method setSecondsCounter:

UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);

To test this change I also removed the UIAccessibilityTraitUpdatesFrequently trait from the text element. When run on the device VoiceOver does indeed attempt to announce the value of the duration every time it changes. Of course since it takes longer than a second to announce the duration it quickly falls behind which is not very useful. So in this situation where the value of an element is changing faster than VoiceOver can announce the changes it is better to use the UIAccessibilityTraitUpdatesFrequently trait.

Whilst on the subject of notifications there is another option which can be useful when you need to make an announcement for an event that does not update the user interface. You can post a UIAccessibilityAnnouncementNotification which causes VoiceOver to announce the NSString that is passed as the second parameter:

UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @”Hello World”);

Wrapping Up

This has been a long post but I hope that if you have made it this far I have convinced you that adding accessibility support to your App is not difficult. If you do not have custom views it is often trivial and requires minimal coding. Even if you do have custom views creating the necessary accessibility container elements is not much more effort. The key point to remember is that the accessibility frames need to be specified in screen coordinates.

The example App that I used for this post can be found here or in my Github CodeExamples repository. I hope you found this post useful and that it will encourage you to ensure your Apps play nice with VoiceOver.