iPhone HowTo: Disable buttons on UIActionSheet

Action sheets are useful modal UI tools and part of the UIKit framework on the iPhone / iPod Touch platform.

Here's a picture of one I knocked up with a few lines of code after using XCode's iPhone application wizard.

UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle:@"Oppian Systems Ltd" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"One", @"Two", @"Three", nil];	
[actionSheet showInView:self.view];
[actionSheet release];

Standard action sheet

The action sheet appears with a nice animated slide from the bottom of the screen and captures all touch events until one of the buttons is pressed by the user (hence 'modal')

More information on action sheets can be found in Apple's official iPhone Human Interface Guidelines pages

Recently, whilst working on a customer project I was asked by a developer new to the platform whether it's possible to disable one of the buttons on the action sheet, so that it would appear dimmed and not react to any touch events.

Well, if you browse the headers and documentation from the iPhone SDK, you may come to the conclusion that this is not possible. There doesn't appear to be any APIs to allow you to achieve this functionality. So I started to look a little deeper ...

Fortunately, due to the type introspection capability of the Objective-C runtime, it's possible to discover additional classes and their respective methods and properties that aren't directly available via the SDK.

Warning:

Using private undocumented APIs may violate your agreement with Apple and lead to your app being rejected from the app store. This article is for informational purposes only.

Whilst lack of any official API to acheive this functionality may have been an oversight, it is more likely by design. i.e Apple probably don't want you to do this. You should probably consider simply omitting a button from the action sheet completely rather than using this technique to disable it.

Discovering the DNA of a UIActionSheet

The action sheet is implemented by the UIActionSheet class that is part of the standard UIKit framework. If you browse to the definition of this class using xcode (CMD+Double click) you will discover that like most interactive controls in UIKit, UIActionSheet is derived from UIView.

Now, we are interested in the buttons on the action sheet, rather than the action sheet itself. We can get a list of the child views of any UIView by using the subviews instance method which will return an NSArray of UIView subviews:

NSArray* subViews = [actionSheet subViews];

Now, as an experienced iPhone developer, you may be expecting at least some of those subviews to be instances of the UIButton class? Experience has taught me to be cautious when making assumptions!

We need to discover the type at runtime. We can do this by using the class method that's available on any class derived from NSObject, which is basically everything in Objective-C! If you iterate over all subviews of UIActionSheet what you will find is that the buttons are actually instances of UIThreePartButton.

 

// Implement the UIActionSheetDelegate protocol
- (void)willPresentActionSheet:(UIActionSheet *)actionSheet
{
    for (UIView* view in [actionSheet subviews])
    {
        NSString* type = [[view class] description];
        // set a breakpoint on some code here and run the debugger
    }
}

 

Now UIThreePartButton is a private class that's not available in the official SDK headers or documentation. Fortunately, to avoid us having to write lots more code to discover the methods and properties available on UIThreePartButton, a quick google reveals that some helpful people have already done it for us!

So now we know that UIThreePartButton is derived from UIPushButton, which is itself derived from UIControl. It's worth pointing out at this point that UIButton does not form part of the inheritance tree for UIThreePartButton. However, a quick look at the header for UIPushButton reveals some potentially useful methods for us to call: title and setEnabled look particularly useful.

But how do we use this class with no headers? We could create our own headers and call the methods directly. However there is dangerous and there is a better and safer way. The danger with directly using private undocumented APIs is that Apple may change them without warning which would probably lead to your app crashing and/or being rejected from the app store. A better way is to use some official and supported methods to determine at runtime whether the APIs exist, and if so call them. This can be done with the respondsToSelector: and performSelector:withObject: methods of NSObject.

Putting it all together, here's how I disabled the button labelled 'Two' in the example above:

- (void)willPresentActionSheet:(UIActionSheet *)actionSheet 
{    
    for (UIView* view in [actionSheet subviews])
    {
        if ([[[view class] description] isEqualToString:@"UIThreePartButton"])
        {			
            if ([view respondsToSelector:@selector(title)])
            {
                NSString* title = [view performSelector:@selector(title)];
                if ([title isEqualToString:@"Two"] && [view respondsToSelector:@selector(setEnabled:)])
                {					
                    [view performSelector:@selector(setEnabled:) withObject:NO];
                }		
            }        
        }   
    }
}

Action sheet with disabled=And here is the result:

By dynamically discovering whether the subviews of UIActionSheet are instances of UIThreePartButton and also testing for the presence of title and setEnabled, we are able to use this functionality in a safe way. If Apple ever change the APIs, our conditional statements will fail and nothing will happen instead of our app crashing. Of course, you should still be able to handle this case in your event handler for any button that is potentially disabled.

You could argue that actually we have used only supported APIs, although more accurately we have only compiled against supported APIs and are using private APIs at runtime.

I'm not sure if even using defensive coding techniques like this to access private APIs is enough to avoid app store rejection though, so consider the possible implications carefully before making use of this technique!

P.S. I have also provided this technique as an answer to a question on StackOverflow - a great site for programming Q&A!

Related tags: action sheet, buttons, cocoa, iphone, objective-c, ui, uikit

Comments are closed.

Comments have been closed for this post.



Copyright © 2009-2011 Oppian Systems Ltd. All rights reserved.

Design by jonwallacedesign.com