More Usability-Oriented Method for Disabling an NSTabViewItem

×

This post was originally published in 2011
It may contain stale & outdated information. Or it may have grown more awesome with age, like the author.

Jump to tutorial

I was unable to find a way to easily disable an NSTabViewItem – before implementing the method described below, I achieved this by simply returning “NO” from the “shouldSelectTabViewItem” delegate method. I don’t like this method as I feel that from the user’s perspective:

  • The reason they can’t click the tab item isn’t clear – is it disabled? Has something broken?
  • If they do guess that the tab has been disabled, there is no way short of trial and error for them to discover how they may enable it

Instead of using this opaque method for disabling a given tab, I opted for a more complicated yet informative approach:

When the user clicks a disabled tab, a modal dialog opens, informing them that the tab has been disabled and why, and offers two options: ‘Continue’, and ‘Enable’. In my case, clicking enable simply checks a checkbox and switches to the disabled tab.

From a usability perspective, this approach is superior for two reasons:

  1. The user is informed of something that is otherwise not immediately apparent
  2. The user is given a ‘one click’ option to perform whatever action is required to remedy the situation

Howto:

Make sure your controller is the delegate for your NSTabView. Either programatically, as shown here in the class implementation or through XCode’s GUI.

AppController.m:

3
4
5
- (void) awakeFromNib {
    [yourTabView setDelegate:self];
}

Then make sure the same class implements the ‘shouldSelectTabViewItem’ delegate method, as below:

This is called when a user attempts to change tabs, and is where we detect the tab they’re attempting to change to, and if that tab is disabled, prevent the app from switching to the tab and show the user a message.

AppController.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem {
	if(![tabViewItem identifier] == yourIdentifier && isDisabled){
 
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Enable"];
 
            [alert setInformativeText:@"Tab Disabled"];
            [alert setMessageText:@"This tab has been disabled"];
 
            [alert setAlertStyle:NSWarningAlertStyle];
 
            [alert beginSheetModalForWindow:[NSApp mainWindow] 
                              modalDelegate:self 
                             didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                                contextInfo:@"Tab Disabled"];
		return NO;	
	} else {
		return YES;	
	}
}

Now we need to implement the ‘alertDidEnd’ delegate method in the same class, which is called when the user clicks one of the buttons we added to the NSAlert above.

AppController.m:

1
2
3
4
5
6
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == @"Tab Disabled" && returnCode == NSAlertSecondButtonReturn) {
         // Perform whatever action is required to enable your tab here, and switch the user to that tab
        [tabsView selectTabViewItemWithIdentifier:yourIdentifier];
    }
}

And that’s it!

Bare-bones header & implementations below:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import
 
#define MODAL_TAB_DISABLED =      @"Tab Disabled"
#define DISABLED_TAB_IDENTIFIER   @"Disabled Tab"
 
@interface AppController : NSObject {
     IBOutlet NSTabView *tabView;
}
 
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
 
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#import <Cocoa/Cocoa.h>
#import <AppController.h>
 
@implementation AppController
 
-(void)awakeFromNib {
     [tabView setDelegate:self];    // Set self as the tab view's delegate
}
 
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem {
	if(![tabViewItem identifier] == DISABLED_TAB_IDENTIFIER && isDisabled){
 
            // Create and configure an alert
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Enable"];
 
            [alert setInformativeText:@"Tab Disabled"];
            [alert setMessageText:@"This tab has been disabled"];
 
            [alert setAlertStyle:NSWarningAlertStyle];
 
            [alert beginSheetModalForWindow:[NSApp mainWindow] 
                              modalDelegate:self 
                             didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                                contextInfo:MODAL_TAB_DISABLED];
		return NO;	
	} else {
		return YES;	
	}
}
 
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == MODAL_TAB_DISABLED && returnCode == NSAlertSecondButtonReturn) {
         // Perform whatever action is required to enable your tab here, and switch the user to that tab
        [tabsView selectTabViewItemWithIdentifier:yourIdentifier];
    }
}
 
@end
No comments | Trackback