validateUserInterfaceItem(3)

前回の更に続き。

今回は、NSControlの方を変更するのではなく、”validateUserInterfaceItem:”の記述の仕方の説明。

しかも、ノーマルな”validateUserInterfaceItem:”の記述方法ではなくて、私がより簡潔だと考えている記述方法です。

validateUserInterfaceItem:とは何か?

UIの有効無効を制御するCocoa標準の手法です。validateUserInterfaceItem登場以前には、toolbarやmenu等でそれぞれ異なったメソッドを使用していた。

現在は、validateUserInterfaceItemを実装する事を推奨している。

validateUserInterfaceItemの役割は、

  • UI有効無効を制御
  • UIにチェックマークを付ける
  • UIにtooltipの設定

等です。

Cocoa標準の表記法では

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    else if([anItem action] == @selector(action2:))
    {
       theResult = YES or NO;
    }
    return theResult; // or NO
}

と、こんな感じで表記します。

validateUserInterfaceItem:の表記法の問題点は何か?

プログラムコードを書く時には、関連するコードは近い場所に書くべきと言われています。 しかし、この標準的な書き方では関係ある物はまとめたいのに、actionのメソッドと有効にするためのロジックが以下のように離れてしまいます。

// UIから実行したいメソッド
- (IBAction) saveAction:(id)sender
{
    .
    .
    .
}
.
.
.
- (IBAction) action2:(id)sender
{
    .
    .
    .
}
.
.
.

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    // saveAction:のenable/disableの制御
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    // action2:のenable/disableの制御
    else if([anItem action] == @selector(action2:))
    {
        .
        .
        .
        .
        .
        .
    }
    return theResult;
}

また、validateUserInterfaceItem:メソッドの中の、if-else-ifが延々と続くのも可読性が悪くなります。

解決方法は?

解決方法は単純です。

UIから呼出されるaction:メソッドとUIのenable/disableを決定するロジックを記述するcanActionメソッドの2つを対にして実装します。 これで2つのコードが離れる事を防げます。

次に、if-else-ifが延々と続くのを防ぐ為に、validateUserInterfaceItem:でaction:メソッドからcanActionメソッドを動的に探すコードを書けば解決出来ます。

これが、

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    if([anItem action] == @selector(saveAction:))
    {
        theResult = [self canSaveAction:anItem];
    }
    else if([anItem action] == @selector(action2:))
    {
        theResult = [self canAction2:anItem];
    }
    else if([anItem action] == @selector(action3:))
    {
        theResult = [self canAction3:anItem];
    }
    else if([anItem action] == @selector(action4:))
    {
        theResult = [self canAction4:anItem];
    }

    return theResult; // or NO
}

こうなります。

#import "NSObject(MTLControlItemEnabler).h"
    .
    .
    .
- (BOOL)validateUserInterfaceItem:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    // controlItemEnablerで全部面倒見ます
    return [self controlItemEnabler:anItem];
}

actionとcanActionメソッドは、例えば以下のように一カ所に表記出来ます。

- (IBAction) saveAction:(id)sender
{
    NSError *error = nil;

    if (![[self managedObjectContext] save:&error])
    {
        [[NSApplication sharedApplication] presentError:error];
    }
}

- (BOOL) canSaveAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [[self managedObjectContext] hasChanges];
}

サンプルコードは、

EnablerTest-2.zip